/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "android.hardware.tv.hdmi.cec" #include #include #include #include #include #include "HdmiCecMock.h" using ndk::ScopedAStatus; namespace android { namespace hardware { namespace tv { namespace hdmi { namespace cec { namespace implementation { void HdmiCecMock::serviceDied(void* cookie) { ALOGE("HdmiCecMock died"); auto hdmiCecMock = static_cast(cookie); hdmiCecMock->closeCallback(); } ScopedAStatus HdmiCecMock::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_return) { // Have a list to maintain logical addresses mLogicalAddresses.push_back(addr); *_aidl_return = Result::SUCCESS; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::clearLogicalAddress() { // Remove logical address from the list mLogicalAddresses = {}; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::enableAudioReturnChannel(int32_t portId __unused, bool enable __unused) { // Maintain ARC status return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::getCecVersion(int32_t* _aidl_return) { // Maintain a cec version and return it *_aidl_return = mCecVersion; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::getPhysicalAddress(int32_t* _aidl_return) { // Maintain a physical address and return it // Default 0xFFFF, update on hotplug event *_aidl_return = mPhysicalAddress; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::getVendorId(int32_t* _aidl_return) { *_aidl_return = mCecVendorId; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::sendMessage(const CecMessage& message, SendMessageResult* _aidl_return) { if (message.body.size() == 0) { *_aidl_return = SendMessageResult::NACK; } else { sendMessageToFifo(message); *_aidl_return = SendMessageResult::SUCCESS; } return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::setCallback(const std::shared_ptr& callback) { closeCallback(); if (callback != nullptr) { mCallback = callback; mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(serviceDied)); AIBinder_linkToDeath(callback->asBinder().get(), mDeathRecipient.get(), this /* cookie */); mInputFile = open(CEC_MSG_IN_FIFO, O_RDWR | O_CLOEXEC); mOutputFile = open(CEC_MSG_OUT_FIFO, O_RDWR | O_CLOEXEC); pthread_create(&mThreadId, NULL, __threadLoop, this); pthread_setname_np(mThreadId, "hdmi_cec_loop"); } return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::setLanguage(const std::string& language) { if (language.size() != 3) { ALOGE("[halimp_aidl] Wrong language code: expected 3 letters, but it was %zu", language.size()); return ScopedAStatus::ok(); } // TODO Validate if language is a valid language code const char* languageStr = language.c_str(); int convertedLanguage = ((languageStr[0] & 0xFF) << 16) | ((languageStr[1] & 0xFF) << 8) | (languageStr[2] & 0xFF); mOptionLanguage = convertedLanguage; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::enableWakeupByOtp(bool value) { mOptionWakeUp = value; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::enableCec(bool value) { mOptionEnableCec = value; return ScopedAStatus::ok(); } ScopedAStatus HdmiCecMock::enableSystemCecControl(bool value) { mOptionSystemCecControl = value; return ScopedAStatus::ok(); } void* HdmiCecMock::__threadLoop(void* user) { HdmiCecMock* const self = static_cast(user); self->threadLoop(); return 0; } int HdmiCecMock::readMessageFromFifo(unsigned char* buf, int msgCount) { if (msgCount <= 0 || !buf) { return 0; } int ret = -1; // Maybe blocked at driver ret = read(mInputFile, buf, msgCount); if (ret < 0) { ALOGE("[halimp_aidl] read :%s failed, ret:%d\n", CEC_MSG_IN_FIFO, ret); return -1; } return ret; } int HdmiCecMock::sendMessageToFifo(const CecMessage& message) { unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH + 1] = {0}; int ret = -1; msgBuf[0] = ((static_cast(message.initiator) & 0xf) << 4) | (static_cast(message.destination) & 0xf); size_t length = std::min(static_cast(message.body.size()), static_cast(CEC_MESSAGE_BODY_MAX_LENGTH)); for (size_t i = 0; i < length; ++i) { msgBuf[i + 1] = static_cast(message.body[i]); } // Open the output pipe for writing outgoing cec message mOutputFile = open(CEC_MSG_OUT_FIFO, O_WRONLY | O_CLOEXEC); if (mOutputFile < 0) { ALOGE("[halimp_aidl] file open failed for writing"); return -1; } // Write message into the output pipe ret = write(mOutputFile, msgBuf, length + 1); close(mOutputFile); if (ret < 0) { ALOGE("[halimp_aidl] write :%s failed, ret:%d\n", CEC_MSG_OUT_FIFO, ret); return -1; } return ret; } void HdmiCecMock::printCecMsgBuf(const char* msg_buf, int len) { int i, size = 0; const int bufSize = CEC_MESSAGE_BODY_MAX_LENGTH * 3; // Use 2 characters for each byte in the message plus 1 space char buf[bufSize] = {0}; // Messages longer than max length will be truncated. for (i = 0; i < len && size < bufSize; i++) { size += sprintf(buf + size, " %02x", msg_buf[i]); } ALOGD("[halimp_aidl] %s, msg:%.*s", __FUNCTION__, size, buf); } void HdmiCecMock::handleCecMessage(unsigned char* msgBuf, int msgSize) { CecMessage message; size_t length = std::min(static_cast(msgSize - 1), static_cast(CEC_MESSAGE_BODY_MAX_LENGTH)); message.body.resize(length); for (size_t i = 0; i < length; ++i) { message.body[i] = static_cast(msgBuf[i + 1]); ALOGD("[halimp_aidl] msg body %x", message.body[i]); } message.initiator = static_cast((msgBuf[0] >> 4) & 0xf); ALOGD("[halimp_aidl] msg init %hhd", message.initiator); message.destination = static_cast((msgBuf[0] >> 0) & 0xf); ALOGD("[halimp_aidl] msg dest %hhd", message.destination); if (mCallback != nullptr) { mCallback->onCecMessage(message); } } void HdmiCecMock::threadLoop() { ALOGD("[halimp_aidl] threadLoop start."); unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH]; int r = -1; // Open the input pipe while (mCecThreadRun && mInputFile < 0) { usleep(1000 * 1000); mInputFile = open(CEC_MSG_IN_FIFO, O_RDONLY | O_CLOEXEC); } ALOGD("[halimp_aidl] file open ok, fd = %d.", mInputFile); while (mCecThreadRun) { if (!mOptionSystemCecControl) { usleep(1000 * 1000); continue; } memset(msgBuf, 0, sizeof(msgBuf)); // Try to get a message from dev. // echo -n -e '\x04\x83' >> /dev/cec r = readMessageFromFifo(msgBuf, CEC_MESSAGE_BODY_MAX_LENGTH); if (r <= 1) { // Ignore received ping messages continue; } printCecMsgBuf((const char*)msgBuf, r); if (((msgBuf[0] >> 4) & 0xf) == 0xf) { // The message is a hotplug event, handled by HDMI HAL. continue; } handleCecMessage(msgBuf, r); } ALOGD("[halimp_aidl] thread end."); } HdmiCecMock::HdmiCecMock() { ALOGD("[halimp_aidl] Opening a virtual CEC HAL for testing and virtual machine."); mCallback = nullptr; mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(nullptr); } void HdmiCecMock::closeCallback() { if (mCallback != nullptr) { ALOGD("[halimp_aidl] HdmiCecMock close the current callback."); mCallback = nullptr; mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(nullptr); mCecThreadRun = false; pthread_join(mThreadId, NULL); } } HdmiCecMock::~HdmiCecMock() { ALOGD("[halimp_aidl] HdmiCecMock shutting down."); closeCallback(); } } // namespace implementation } // namespace cec } // namespace hdmi } // namespace tv } // namespace hardware } // namespace android