/* * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define LOG_TAG "vendor.qti.vibrator" #include #include #include #include #include #include #include #include #include "include/Vibrator.h" namespace aidl { namespace android { namespace hardware { namespace vibrator { #define STRONG_MAGNITUDE 0x7fff #define MEDIUM_MAGNITUDE 0x5fff #define LIGHT_MAGNITUDE 0x3fff #define INVALID_VALUE -1 #define CUSTOM_DATA_LEN 3 #define NAME_BUF_SIZE 32 #define MSM_CPU_LAHAINA 415 #define APQ_CPU_LAHAINA 439 #define MSM_CPU_SHIMA 450 #define MSM_CPU_SM8325 501 #define APQ_CPU_SM8325P 502 #define MSM_CPU_YUPIK 475 #define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)%8))) InputFFDevice::InputFFDevice() { DIR *dp; FILE *fp = NULL; struct dirent *dir; uint8_t ffBitmask[FF_CNT / 8]; char devicename[PATH_MAX]; const char *INPUT_DIR = "/dev/input/"; char name[NAME_BUF_SIZE]; int fd, ret; int soc = property_get_int32("ro.vendor.qti.soc_id", -1); mVibraFd = INVALID_VALUE; mSupportGain = false; mSupportEffects = false; mSupportExternalControl = false; mCurrAppId = INVALID_VALUE; mCurrMagnitude = 0x7fff; mInExternalControl = false; dp = opendir(INPUT_DIR); if (!dp) { ALOGE("open %s failed, errno = %d", INPUT_DIR, errno); return; } memset(ffBitmask, 0, sizeof(ffBitmask)); while ((dir = readdir(dp)) != NULL){ if (dir->d_name[0] == '.' && (dir->d_name[1] == '\0' || (dir->d_name[1] == '.' && dir->d_name[2] == '\0'))) continue; snprintf(devicename, PATH_MAX, "%s%s", INPUT_DIR, dir->d_name); fd = TEMP_FAILURE_RETRY(open(devicename, O_RDWR)); if (fd < 0) { ALOGE("open %s failed, errno = %d", devicename, errno); continue; } ret = TEMP_FAILURE_RETRY(ioctl(fd, EVIOCGNAME(sizeof(name)), name)); if (ret == -1) { ALOGE("get input device name %s failed, errno = %d\n", devicename, errno); close(fd); continue; } if (strcmp(name, "qcom-hv-haptics") && strcmp(name, "qti-haptics") && strcmp(name, "aw8697_haptic")) { ALOGD("not a qcom/qti haptics device\n"); close(fd); continue; } ALOGI("%s is detected at %s\n", name, devicename); ret = TEMP_FAILURE_RETRY(ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffBitmask)), ffBitmask)); if (ret == -1) { ALOGE("ioctl failed, errno = %d", errno); close(fd); continue; } if (test_bit(FF_CONSTANT, ffBitmask) || test_bit(FF_PERIODIC, ffBitmask)) { mVibraFd = fd; if (test_bit(FF_CUSTOM, ffBitmask)) mSupportEffects = true; if (test_bit(FF_GAIN, ffBitmask)) mSupportGain = true; if (soc <= 0 && (fp = fopen("/sys/devices/soc0/soc_id", "r")) != NULL) { fscanf(fp, "%u", &soc); fclose(fp); } switch (soc) { case MSM_CPU_LAHAINA: case APQ_CPU_LAHAINA: case MSM_CPU_SHIMA: case MSM_CPU_SM8325: case APQ_CPU_SM8325P: case MSM_CPU_YUPIK: mSupportExternalControl = true; break; default: mSupportExternalControl = false; break; } break; } close(fd); } closedir(dp); } /** Play vibration * * @param effectId: ID of the predefined effect will be played. If effectId is valid * (non-negative value), the timeoutMs value will be ignored, and the * real playing length will be set in param@playLengtMs and returned * to VibratorService. If effectId is invalid, value in param@timeoutMs * will be used as the play length for playing a constant effect. * @param timeoutMs: playing length, non-zero means playing, zero means stop playing. * @param playLengthMs: the playing length in ms unit which will be returned to * VibratorService if the request is playing a predefined effect. * The custom_data in periodic is reused for returning the playLengthMs * from kernel space to userspace if the pattern is defined in kernel * driver. It's been defined with following format: * . * The effect-ID is used for passing down the predefined effect to * kernel driver, and the rest two parameters are used for returning * back the real playing length from kernel driver. */ int InputFFDevice::play(int effectId, uint32_t timeoutMs, long *playLengthMs) { struct ff_effect effect; struct input_event play; int16_t data[CUSTOM_DATA_LEN] = {0, 0, 0}; int ret; /* For QMAA compliance, return OK even if vibrator device doesn't exist */ if (mVibraFd == INVALID_VALUE) { if (playLengthMs != NULL) *playLengthMs = 0; return 0; } if (timeoutMs != 0) { if (mCurrAppId != INVALID_VALUE) { ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCRMFF, mCurrAppId)); if (ret == -1) { ALOGE("ioctl EVIOCRMFF failed, errno = %d", -errno); goto errout; } mCurrAppId = INVALID_VALUE; } memset(&effect, 0, sizeof(effect)); if (effectId != INVALID_VALUE) { data[0] = effectId; effect.type = FF_PERIODIC; effect.u.periodic.waveform = FF_CUSTOM; effect.u.periodic.magnitude = mCurrMagnitude; effect.u.periodic.custom_data = data; effect.u.periodic.custom_len = sizeof(int16_t) * CUSTOM_DATA_LEN; } else { effect.type = FF_CONSTANT; effect.u.constant.level = mCurrMagnitude; effect.replay.length = timeoutMs; } effect.id = mCurrAppId; effect.replay.delay = 0; ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCSFF, &effect)); if (ret == -1) { ALOGE("ioctl EVIOCSFF failed, errno = %d", -errno); goto errout; } mCurrAppId = effect.id; if (effectId != INVALID_VALUE && playLengthMs != NULL) { *playLengthMs = data[1] * 1000 + data[2]; } play.value = 1; play.type = EV_FF; play.code = mCurrAppId; play.time.tv_sec = 0; play.time.tv_usec = 0; ret = TEMP_FAILURE_RETRY(write(mVibraFd, (const void*)&play, sizeof(play))); if (ret == -1) { ALOGE("write failed, errno = %d\n", -errno); ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCRMFF, mCurrAppId)); if (ret == -1) ALOGE("ioctl EVIOCRMFF failed, errno = %d", -errno); goto errout; } } else if (mCurrAppId != INVALID_VALUE) { ret = TEMP_FAILURE_RETRY(ioctl(mVibraFd, EVIOCRMFF, mCurrAppId)); if (ret == -1) { ALOGE("ioctl EVIOCRMFF failed, errno = %d", -errno); goto errout; } mCurrAppId = INVALID_VALUE; } return 0; errout: mCurrAppId = INVALID_VALUE; return ret; } int InputFFDevice::on(int32_t timeoutMs) { return play(INVALID_VALUE, timeoutMs, NULL); } int InputFFDevice::off() { return play(INVALID_VALUE, 0, NULL); } int InputFFDevice::setAmplitude(uint8_t amplitude) { int tmp, ret; struct input_event ie; /* For QMAA compliance, return OK even if vibrator device doesn't exist */ if (mVibraFd == INVALID_VALUE) return 0; tmp = amplitude * (STRONG_MAGNITUDE - LIGHT_MAGNITUDE) / 255; tmp += LIGHT_MAGNITUDE; ie.type = EV_FF; ie.code = FF_GAIN; ie.value = tmp; ret = TEMP_FAILURE_RETRY(write(mVibraFd, &ie, sizeof(ie))); if (ret == -1) { ALOGE("write FF_GAIN failed, errno = %d", -errno); return ret; } mCurrMagnitude = tmp; return 0; } int InputFFDevice::playEffect(int effectId, EffectStrength es, long *playLengthMs) { switch (es) { case EffectStrength::LIGHT: mCurrMagnitude = LIGHT_MAGNITUDE; break; case EffectStrength::MEDIUM: mCurrMagnitude = MEDIUM_MAGNITUDE; break; case EffectStrength::STRONG: mCurrMagnitude = STRONG_MAGNITUDE; break; default: return -1; } return play(effectId, INVALID_VALUE, playLengthMs); } ndk::ScopedAStatus Vibrator::getCapabilities(int32_t* _aidl_return) { *_aidl_return = IVibrator::CAP_ON_CALLBACK; if (ff.mSupportGain) *_aidl_return |= IVibrator::CAP_AMPLITUDE_CONTROL; if (ff.mSupportEffects) *_aidl_return |= IVibrator::CAP_PERFORM_CALLBACK; if (ff.mSupportExternalControl) *_aidl_return |= IVibrator::CAP_EXTERNAL_CONTROL; ALOGD("QTI Vibrator reporting capabilities: %d", *_aidl_return); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::off() { int ret; ALOGD("QTI Vibrator off"); ret = ff.off(); if (ret != 0) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC)); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs, const std::shared_ptr& callback) { int ret; ALOGD("Vibrator on for timeoutMs: %d", timeoutMs); ret = ff.on(timeoutMs); if (ret != 0) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC)); if (callback != nullptr) { std::thread([=] { ALOGD("Starting on on another thread"); usleep(timeoutMs * 1000); ALOGD("Notifying on complete"); if (!callback->onComplete().isOk()) { ALOGE("Failed to call onComplete"); } }).detach(); } return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength es, const std::shared_ptr& callback, int32_t* _aidl_return) { long playLengthMs; int ret; ALOGD("Vibrator perform effect %d", effect); if (effect < Effect::CLICK || effect > Effect::HEAVY_CLICK) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); if (es != EffectStrength::LIGHT && es != EffectStrength::MEDIUM && es != EffectStrength::STRONG) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); ret = ff.playEffect((static_cast(effect)), es, &playLengthMs); if (ret != 0) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC)); if (callback != nullptr) { std::thread([=] { ALOGD("Starting perform on another thread"); usleep(playLengthMs * 1000); ALOGD("Notifying perform complete"); callback->onComplete(); }).detach(); } *_aidl_return = playLengthMs; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector* _aidl_return) { *_aidl_return = {Effect::CLICK, Effect::DOUBLE_CLICK, Effect::TICK, Effect::THUD, Effect::POP, Effect::HEAVY_CLICK}; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) { uint8_t tmp; int ret; ALOGD("Vibrator set amplitude: %f", amplitude); if (amplitude <= 0.0f || amplitude > 1.0f) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT)); if (ff.mInExternalControl) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); tmp = (uint8_t)(amplitude * 0xff); ret = ff.setAmplitude(tmp); if (ret != 0) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_SERVICE_SPECIFIC)); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) { ALOGD("Vibrator set external control: %d", enabled); if (!ff.mSupportExternalControl) return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); ff.mInExternalControl = enabled; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t* maxDelayMs __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t* maxSize __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector* supported __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive __unused, int32_t* durationMs __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } ndk::ScopedAStatus Vibrator::compose(const std::vector& composite __unused, const std::shared_ptr& callback __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector* _aidl_return __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t id __unused, Effect effect __unused, EffectStrength strength __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t id __unused) { return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_UNSUPPORTED_OPERATION)); } } // namespace vibrator } // namespace hardware } // namespace android } // namespace aidl