fifo.cpp revision 7cc8f545385d09aaa0c6e081856d7e3c8e500133
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17//#define LOG_NDEBUG 0 18#define LOG_TAG "audio_utils_fifo" 19 20#include <errno.h> 21#include <limits.h> 22#include <stdlib.h> 23#include <string.h> 24 25#include <audio_utils/clock_nanosleep.h> 26#include <audio_utils/fifo.h> 27#include <audio_utils/futex.h> 28#include <audio_utils/roundup.h> 29#include <cutils/log.h> 30#include <utils/Errors.h> 31 32audio_utils_fifo_base::audio_utils_fifo_base(uint32_t frameCount, 33 audio_utils_fifo_index& writerRear, audio_utils_fifo_index *throttleFront) 34 __attribute__((no_sanitize("integer"))) : 35 mFrameCount(frameCount), mFrameCountP2(roundup(frameCount)), 36 mFudgeFactor(mFrameCountP2 - mFrameCount), 37 // FIXME need an API to configure the sync types 38 mWriterRear(writerRear), mWriterRearSync(AUDIO_UTILS_FIFO_SYNC_SHARED), 39 mThrottleFront(throttleFront), mThrottleFrontSync(AUDIO_UTILS_FIFO_SYNC_SHARED), 40 mIsShutdown(false) 41{ 42 // actual upper bound on frameCount will depend on the frame size 43 LOG_ALWAYS_FATAL_IF(frameCount == 0 || frameCount > ((uint32_t) INT32_MAX)); 44} 45 46audio_utils_fifo_base::~audio_utils_fifo_base() 47{ 48} 49 50uint32_t audio_utils_fifo_base::sum(uint32_t index, uint32_t increment) const 51 __attribute__((no_sanitize("integer"))) 52{ 53 if (mFudgeFactor) { 54 uint32_t mask = mFrameCountP2 - 1; 55 ALOG_ASSERT((index & mask) < mFrameCount); 56 ALOG_ASSERT(increment <= mFrameCountP2); 57 if ((index & mask) + increment >= mFrameCount) { 58 increment += mFudgeFactor; 59 } 60 index += increment; 61 ALOG_ASSERT((index & mask) < mFrameCount); 62 return index; 63 } else { 64 return index + increment; 65 } 66} 67 68int32_t audio_utils_fifo_base::diff(uint32_t rear, uint32_t front, size_t *lost) const 69 __attribute__((no_sanitize("integer"))) 70{ 71 // TODO replace multiple returns by a single return point so this isn't needed 72 if (lost != NULL) { 73 *lost = 0; 74 } 75 if (mIsShutdown) { 76 return -EIO; 77 } 78 uint32_t diff = rear - front; 79 if (mFudgeFactor) { 80 uint32_t mask = mFrameCountP2 - 1; 81 uint32_t rearOffset = rear & mask; 82 uint32_t frontOffset = front & mask; 83 if (rearOffset >= mFrameCount || frontOffset >= mFrameCount) { 84 ALOGE("%s frontOffset=%u rearOffset=%u mFrameCount=%u", 85 __func__, frontOffset, rearOffset, mFrameCount); 86 shutdown(); 87 return -EIO; 88 } 89 uint32_t genDiff = (rear & ~mask) - (front & ~mask); 90 if (genDiff != 0) { 91 if (genDiff > mFrameCountP2) { 92 if (lost != NULL) { 93 // TODO provide a more accurate estimate 94 *lost = (genDiff / mFrameCountP2) * mFrameCount; 95 } 96 return -EOVERFLOW; 97 } 98 diff -= mFudgeFactor; 99 } 100 } 101 // FIFO should not be overfull 102 if (diff > mFrameCount) { 103 if (lost != NULL) { 104 *lost = diff; 105 } 106 return -EOVERFLOW; 107 } 108 return (int32_t) diff; 109} 110 111void audio_utils_fifo_base::shutdown() const 112{ 113 ALOGE("%s", __func__); 114 mIsShutdown = true; 115} 116 117//////////////////////////////////////////////////////////////////////////////// 118 119audio_utils_fifo::audio_utils_fifo(uint32_t frameCount, uint32_t frameSize, void *buffer, 120 audio_utils_fifo_index& writerRear, audio_utils_fifo_index *throttleFront) 121 __attribute__((no_sanitize("integer"))) : 122 audio_utils_fifo_base(frameCount, writerRear, throttleFront), 123 mFrameSize(frameSize), mBuffer(buffer) 124{ 125 // maximum value of frameCount * frameSize is INT32_MAX (2^31 - 1), not 2^31, because we need to 126 // be able to distinguish successful and error return values from read and write. 127 LOG_ALWAYS_FATAL_IF(frameCount == 0 || frameSize == 0 || buffer == NULL || 128 frameCount > ((uint32_t) INT32_MAX) / frameSize); 129} 130 131audio_utils_fifo::audio_utils_fifo(uint32_t frameCount, uint32_t frameSize, void *buffer, 132 bool throttlesWriter) : 133 audio_utils_fifo(frameCount, frameSize, buffer, mSingleProcessSharedRear, 134 throttlesWriter ? &mSingleProcessSharedFront : NULL) 135{ 136} 137 138audio_utils_fifo::~audio_utils_fifo() 139{ 140} 141 142//////////////////////////////////////////////////////////////////////////////// 143 144audio_utils_fifo_provider::audio_utils_fifo_provider(audio_utils_fifo& fifo) : 145 mFifo(fifo), mObtained(0), mTotalReleased(0) 146{ 147} 148 149audio_utils_fifo_provider::~audio_utils_fifo_provider() 150{ 151} 152 153//////////////////////////////////////////////////////////////////////////////// 154 155audio_utils_fifo_writer::audio_utils_fifo_writer(audio_utils_fifo& fifo) : 156 audio_utils_fifo_provider(fifo), mLocalRear(0), 157 mArmLevel(fifo.mFrameCount), mTriggerLevel(0), 158 mIsArmed(true), // because initial fill level of zero is < mArmLevel 159 mEffectiveFrames(fifo.mFrameCount) 160{ 161} 162 163audio_utils_fifo_writer::~audio_utils_fifo_writer() 164{ 165} 166 167ssize_t audio_utils_fifo_writer::write(const void *buffer, size_t count, 168 const struct timespec *timeout) 169 __attribute__((no_sanitize("integer"))) 170{ 171 audio_utils_iovec iovec[2]; 172 ssize_t availToWrite = obtain(iovec, count, timeout); 173 if (availToWrite > 0) { 174 memcpy((char *) mFifo.mBuffer + iovec[0].mOffset * mFifo.mFrameSize, buffer, 175 iovec[0].mLength * mFifo.mFrameSize); 176 if (iovec[1].mLength > 0) { 177 memcpy((char *) mFifo.mBuffer + iovec[1].mOffset * mFifo.mFrameSize, 178 (char *) buffer + (iovec[0].mLength * mFifo.mFrameSize), 179 iovec[1].mLength * mFifo.mFrameSize); 180 } 181 release(availToWrite); 182 } 183 return availToWrite; 184} 185 186// iovec == NULL is not part of the public API, but internally it means don't set mObtained 187ssize_t audio_utils_fifo_writer::obtain(audio_utils_iovec iovec[2], size_t count, 188 const struct timespec *timeout) 189 __attribute__((no_sanitize("integer"))) 190{ 191 int err = 0; 192 size_t availToWrite; 193 if (mFifo.mThrottleFront != NULL) { 194 int retries = kRetries; 195 uint32_t front; 196 for (;;) { 197 front = mFifo.mThrottleFront->loadAcquire(); 198 // returns -EIO if mIsShutdown 199 int32_t filled = mFifo.diff(mLocalRear, front); 200 if (filled < 0) { 201 // on error, return an empty slice 202 err = filled; 203 availToWrite = 0; 204 break; 205 } 206 availToWrite = mEffectiveFrames > (uint32_t) filled ? 207 mEffectiveFrames - (uint32_t) filled : 0; 208 // TODO pull out "count == 0" 209 if (count == 0 || availToWrite > 0 || timeout == NULL || 210 (timeout->tv_sec == 0 && timeout->tv_nsec == 0)) { 211 break; 212 } 213 // TODO add comments 214 // TODO abstract out switch and replace by general sync object 215 // the high level code (synchronization, sleep, futex, iovec) should be completely 216 // separate from the low level code (indexes, available, masking). 217 int op = FUTEX_WAIT; 218 switch (mFifo.mThrottleFrontSync) { 219 case AUDIO_UTILS_FIFO_SYNC_SLEEP: 220 err = audio_utils_clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, timeout, 221 NULL /*remain*/); 222 if (err < 0) { 223 LOG_ALWAYS_FATAL_IF(errno != EINTR, "unexpected err=%d errno=%d", err, errno); 224 err = -errno; 225 } else { 226 err = -ETIMEDOUT; 227 } 228 break; 229 case AUDIO_UTILS_FIFO_SYNC_PRIVATE: 230 op = FUTEX_WAIT_PRIVATE; 231 // fall through 232 case AUDIO_UTILS_FIFO_SYNC_SHARED: 233 if (timeout->tv_sec == LONG_MAX) { 234 timeout = NULL; 235 } 236 err = mFifo.mThrottleFront->wait(op, front, timeout); 237 if (err < 0) { 238 switch (errno) { 239 case EWOULDBLOCK: 240 // Benign race condition with partner: mFifo.mThrottleFront->mIndex 241 // changed value between the earlier atomic_load_explicit() and sys_futex(). 242 // Try to load index again, but give up if we are unable to converge. 243 if (retries-- > 0) { 244 // bypass the "timeout = NULL;" below 245 continue; 246 } 247 // fall through 248 case EINTR: 249 case ETIMEDOUT: 250 err = -errno; 251 break; 252 default: 253 LOG_ALWAYS_FATAL("unexpected err=%d errno=%d", err, errno); 254 break; 255 } 256 } 257 break; 258 default: 259 LOG_ALWAYS_FATAL("mFifo.mThrottleFrontSync=%d", mFifo.mThrottleFrontSync); 260 break; 261 } 262 timeout = NULL; 263 } 264 } else { 265 if (mFifo.mIsShutdown) { 266 err = -EIO; 267 availToWrite = 0; 268 } else { 269 availToWrite = mEffectiveFrames; 270 } 271 } 272 if (availToWrite > count) { 273 availToWrite = count; 274 } 275 uint32_t rearOffset = mLocalRear & (mFifo.mFrameCountP2 - 1); 276 size_t part1 = mFifo.mFrameCount - rearOffset; 277 if (part1 > availToWrite) { 278 part1 = availToWrite; 279 } 280 size_t part2 = part1 > 0 ? availToWrite - part1 : 0; 281 // return slice 282 if (iovec != NULL) { 283 iovec[0].mOffset = rearOffset; 284 iovec[0].mLength = part1; 285 iovec[1].mOffset = 0; 286 iovec[1].mLength = part2; 287 mObtained = availToWrite; 288 } 289 return availToWrite > 0 ? availToWrite : err; 290} 291 292void audio_utils_fifo_writer::release(size_t count) 293 __attribute__((no_sanitize("integer"))) 294{ 295 // no need to do an early check for mIsShutdown, because the extra code executed is harmless 296 if (count > 0) { 297 if (count > mObtained) { 298 ALOGE("%s(count=%zu) > mObtained=%u", __func__, count, mObtained); 299 mFifo.shutdown(); 300 return; 301 } 302 if (mFifo.mThrottleFront != NULL) { 303 uint32_t front = mFifo.mThrottleFront->loadAcquire(); 304 // returns -EIO if mIsShutdown 305 int32_t filled = mFifo.diff(mLocalRear, front); 306 mLocalRear = mFifo.sum(mLocalRear, count); 307 mFifo.mWriterRear.storeRelease(mLocalRear); 308 // TODO add comments 309 int op = FUTEX_WAKE; 310 switch (mFifo.mWriterRearSync) { 311 case AUDIO_UTILS_FIFO_SYNC_SLEEP: 312 break; 313 case AUDIO_UTILS_FIFO_SYNC_PRIVATE: 314 op = FUTEX_WAKE_PRIVATE; 315 // fall through 316 case AUDIO_UTILS_FIFO_SYNC_SHARED: 317 if (filled >= 0) { 318 if ((uint32_t) filled < mArmLevel) { 319 mIsArmed = true; 320 } 321 if (mIsArmed && filled + count > mTriggerLevel) { 322 int err = mFifo.mWriterRear.wake(op, INT32_MAX /*waiters*/); 323 // err is number of processes woken up 324 if (err < 0) { 325 LOG_ALWAYS_FATAL("%s: unexpected err=%d errno=%d", 326 __func__, err, errno); 327 } 328 mIsArmed = false; 329 } 330 } 331 break; 332 default: 333 LOG_ALWAYS_FATAL("mFifo.mWriterRearSync=%d", mFifo.mWriterRearSync); 334 break; 335 } 336 } else { 337 mLocalRear = mFifo.sum(mLocalRear, count); 338 mFifo.mWriterRear.storeRelease(mLocalRear); 339 } 340 mObtained -= count; 341 mTotalReleased += count; 342 } 343} 344 345ssize_t audio_utils_fifo_writer::available() 346{ 347 // iovec == NULL is not part of the public API, but internally it means don't set mObtained 348 return obtain(NULL /*iovec*/, SIZE_MAX /*count*/, NULL /*timeout*/); 349} 350 351void audio_utils_fifo_writer::resize(uint32_t frameCount) 352{ 353 // cap to range [0, mFifo.mFrameCount] 354 if (frameCount > mFifo.mFrameCount) { 355 frameCount = mFifo.mFrameCount; 356 } 357 // if we reduce the effective frame count, update hysteresis points to be within the new range 358 if (frameCount < mEffectiveFrames) { 359 if (mArmLevel > frameCount) { 360 mArmLevel = frameCount; 361 } 362 if (mTriggerLevel > frameCount) { 363 mTriggerLevel = frameCount; 364 } 365 } 366 mEffectiveFrames = frameCount; 367} 368 369uint32_t audio_utils_fifo_writer::size() const 370{ 371 return mEffectiveFrames; 372} 373 374void audio_utils_fifo_writer::setHysteresis(uint32_t lowLevelArm, uint32_t highLevelTrigger) 375{ 376 // cap to range [0, mEffectiveFrames] 377 if (lowLevelArm > mEffectiveFrames) { 378 lowLevelArm = mEffectiveFrames; 379 } 380 if (highLevelTrigger > mEffectiveFrames) { 381 highLevelTrigger = mEffectiveFrames; 382 } 383 // TODO this is overly conservative; it would be better to arm based on actual fill level 384 if (lowLevelArm > mArmLevel) { 385 mIsArmed = true; 386 } 387 mArmLevel = lowLevelArm; 388 mTriggerLevel = highLevelTrigger; 389} 390 391void audio_utils_fifo_writer::getHysteresis(uint32_t *armLevel, uint32_t *triggerLevel) const 392{ 393 *armLevel = mArmLevel; 394 *triggerLevel = mTriggerLevel; 395} 396 397//////////////////////////////////////////////////////////////////////////////// 398 399audio_utils_fifo_reader::audio_utils_fifo_reader(audio_utils_fifo& fifo, bool throttlesWriter) : 400 audio_utils_fifo_provider(fifo), mLocalFront(0), 401 mThrottleFront(throttlesWriter ? mFifo.mThrottleFront : NULL), 402 mArmLevel(-1), mTriggerLevel(mFifo.mFrameCount), 403 mIsArmed(true), // because initial fill level of zero is > mArmLevel 404 mTotalLost(0), mTotalFlushed(0) 405{ 406} 407 408audio_utils_fifo_reader::~audio_utils_fifo_reader() 409{ 410 // TODO Need a way to pass throttle capability to the another reader, should one reader exit. 411} 412 413ssize_t audio_utils_fifo_reader::read(void *buffer, size_t count, const struct timespec *timeout, 414 size_t *lost) 415 __attribute__((no_sanitize("integer"))) 416{ 417 audio_utils_iovec iovec[2]; 418 ssize_t availToRead = obtain(iovec, count, timeout, lost); 419 if (availToRead > 0) { 420 memcpy(buffer, (char *) mFifo.mBuffer + iovec[0].mOffset * mFifo.mFrameSize, 421 iovec[0].mLength * mFifo.mFrameSize); 422 if (iovec[1].mLength > 0) { 423 memcpy((char *) buffer + (iovec[0].mLength * mFifo.mFrameSize), 424 (char *) mFifo.mBuffer + iovec[1].mOffset * mFifo.mFrameSize, 425 iovec[1].mLength * mFifo.mFrameSize); 426 } 427 release(availToRead); 428 } 429 return availToRead; 430} 431 432ssize_t audio_utils_fifo_reader::obtain(audio_utils_iovec iovec[2], size_t count, 433 const struct timespec *timeout) 434 __attribute__((no_sanitize("integer"))) 435{ 436 return obtain(iovec, count, timeout, NULL /*lost*/); 437} 438 439void audio_utils_fifo_reader::release(size_t count) 440 __attribute__((no_sanitize("integer"))) 441{ 442 // no need to do an early check for mIsShutdown, because the extra code executed is harmless 443 if (count > 0) { 444 if (count > mObtained) { 445 ALOGE("%s(count=%zu) > mObtained=%u", __func__, count, mObtained); 446 mFifo.shutdown(); 447 return; 448 } 449 if (mThrottleFront != NULL) { 450 uint32_t rear = mFifo.mWriterRear.loadAcquire(); 451 // returns -EIO if mIsShutdown 452 int32_t filled = mFifo.diff(rear, mLocalFront); 453 mLocalFront = mFifo.sum(mLocalFront, count); 454 mThrottleFront->storeRelease(mLocalFront); 455 // TODO add comments 456 int op = FUTEX_WAKE; 457 switch (mFifo.mThrottleFrontSync) { 458 case AUDIO_UTILS_FIFO_SYNC_SLEEP: 459 break; 460 case AUDIO_UTILS_FIFO_SYNC_PRIVATE: 461 op = FUTEX_WAKE_PRIVATE; 462 // fall through 463 case AUDIO_UTILS_FIFO_SYNC_SHARED: 464 if (filled >= 0) { 465 if (filled > mArmLevel) { 466 mIsArmed = true; 467 } 468 if (mIsArmed && filled - count < mTriggerLevel) { 469 int err = mThrottleFront->wake(op, 1 /*waiters*/); 470 // err is number of processes woken up 471 if (err < 0 || err > 1) { 472 LOG_ALWAYS_FATAL("%s: unexpected err=%d errno=%d", 473 __func__, err, errno); 474 } 475 mIsArmed = false; 476 } 477 } 478 break; 479 default: 480 LOG_ALWAYS_FATAL("mFifo.mThrottleFrontSync=%d", mFifo.mThrottleFrontSync); 481 break; 482 } 483 } else { 484 mLocalFront = mFifo.sum(mLocalFront, count); 485 } 486 mObtained -= count; 487 mTotalReleased += count; 488 } 489} 490 491// iovec == NULL is not part of the public API, but internally it means don't set mObtained 492ssize_t audio_utils_fifo_reader::obtain(audio_utils_iovec iovec[2], size_t count, 493 const struct timespec *timeout, size_t *lost) 494 __attribute__((no_sanitize("integer"))) 495{ 496 int err = 0; 497 int retries = kRetries; 498 uint32_t rear; 499 for (;;) { 500 rear = mFifo.mWriterRear.loadAcquire(); 501 // TODO pull out "count == 0" 502 if (count == 0 || rear != mLocalFront || timeout == NULL || 503 (timeout->tv_sec == 0 && timeout->tv_nsec == 0)) { 504 break; 505 } 506 // TODO add comments 507 int op = FUTEX_WAIT; 508 switch (mFifo.mWriterRearSync) { 509 case AUDIO_UTILS_FIFO_SYNC_SLEEP: 510 err = audio_utils_clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, timeout, 511 NULL /*remain*/); 512 if (err < 0) { 513 LOG_ALWAYS_FATAL_IF(errno != EINTR, "unexpected err=%d errno=%d", err, errno); 514 err = -errno; 515 } else { 516 err = -ETIMEDOUT; 517 } 518 break; 519 case AUDIO_UTILS_FIFO_SYNC_PRIVATE: 520 op = FUTEX_WAIT_PRIVATE; 521 // fall through 522 case AUDIO_UTILS_FIFO_SYNC_SHARED: 523 if (timeout->tv_sec == LONG_MAX) { 524 timeout = NULL; 525 } 526 err = mFifo.mWriterRear.wait(op, rear, timeout); 527 if (err < 0) { 528 switch (errno) { 529 case EWOULDBLOCK: 530 // Benign race condition with partner: mFifo.mWriterRear->mIndex 531 // changed value between the earlier atomic_load_explicit() and sys_futex(). 532 // Try to load index again, but give up if we are unable to converge. 533 if (retries-- > 0) { 534 // bypass the "timeout = NULL;" below 535 continue; 536 } 537 // fall through 538 case EINTR: 539 case ETIMEDOUT: 540 err = -errno; 541 break; 542 default: 543 LOG_ALWAYS_FATAL("unexpected err=%d errno=%d", err, errno); 544 break; 545 } 546 } 547 break; 548 default: 549 LOG_ALWAYS_FATAL("mFifo.mWriterRearSync=%d", mFifo.mWriterRearSync); 550 break; 551 } 552 timeout = NULL; 553 } 554 size_t ourLost; 555 if (lost == NULL) { 556 lost = &ourLost; 557 } 558 // returns -EIO if mIsShutdown 559 int32_t filled = mFifo.diff(rear, mLocalFront, lost); 560 mTotalLost += *lost; 561 mTotalReleased += *lost; 562 if (filled < 0) { 563 if (filled == -EOVERFLOW) { 564 mLocalFront = rear; 565 } 566 // on error, return an empty slice 567 err = filled; 568 filled = 0; 569 } 570 size_t availToRead = (size_t) filled; 571 if (availToRead > count) { 572 availToRead = count; 573 } 574 uint32_t frontOffset = mLocalFront & (mFifo.mFrameCountP2 - 1); 575 size_t part1 = mFifo.mFrameCount - frontOffset; 576 if (part1 > availToRead) { 577 part1 = availToRead; 578 } 579 size_t part2 = part1 > 0 ? availToRead - part1 : 0; 580 // return slice 581 if (iovec != NULL) { 582 iovec[0].mOffset = frontOffset; 583 iovec[0].mLength = part1; 584 iovec[1].mOffset = 0; 585 iovec[1].mLength = part2; 586 mObtained = availToRead; 587 } 588 return availToRead > 0 ? availToRead : err; 589} 590 591ssize_t audio_utils_fifo_reader::available() 592{ 593 return available(NULL /*lost*/); 594} 595 596ssize_t audio_utils_fifo_reader::available(size_t *lost) 597{ 598 // iovec == NULL is not part of the public API, but internally it means don't set mObtained 599 return obtain(NULL /*iovec*/, SIZE_MAX /*count*/, NULL /*timeout*/, lost); 600} 601 602ssize_t audio_utils_fifo_reader::flush(size_t *lost) 603{ 604 audio_utils_iovec iovec[2]; 605 ssize_t ret = obtain(iovec, SIZE_MAX /*count*/, NULL /*timeout*/, lost); 606 if (ret > 0) { 607 size_t flushed = (size_t) ret; 608 release(flushed); 609 mTotalFlushed += flushed; 610 ret = flushed; 611 } 612 return ret; 613} 614 615void audio_utils_fifo_reader::setHysteresis(int32_t armLevel, uint32_t triggerLevel) 616{ 617 // cap to range [0, mFifo.mFrameCount] 618 if (armLevel < 0) { 619 armLevel = -1; 620 } else if ((uint32_t) armLevel > mFifo.mFrameCount) { 621 armLevel = mFifo.mFrameCount; 622 } 623 if (triggerLevel > mFifo.mFrameCount) { 624 triggerLevel = mFifo.mFrameCount; 625 } 626 // TODO this is overly conservative; it would be better to arm based on actual fill level 627 if (armLevel < mArmLevel) { 628 mIsArmed = true; 629 } 630 mArmLevel = armLevel; 631 mTriggerLevel = triggerLevel; 632} 633 634void audio_utils_fifo_reader::getHysteresis(int32_t *armLevel, uint32_t *triggerLevel) const 635{ 636 *armLevel = mArmLevel; 637 *triggerLevel = mTriggerLevel; 638} 639