From 65165d5b94fe05955896d6fddfb734d92a2633d6 Mon Sep 17 00:00:00 2001 From: Shraddha Basantwani Date: Wed, 23 Jun 2021 20:42:08 +0530 Subject: [PATCH] CEC: Add support for multiple ports to default HdmiCec Bug: 185434120 Test: manual Change-Id: I4e1a1f2ff7cb4530e8b223169d8efd452412089b --- tv/cec/1.0/default/Android.bp | 1 + tv/cec/1.0/default/HdmiCecDefault.cpp | 222 ++++++++++++++------------ tv/cec/1.0/default/HdmiCecDefault.h | 15 +- tv/cec/1.0/default/HdmiCecPort.cpp | 101 ++++++++++++ tv/cec/1.0/default/HdmiCecPort.h | 42 +++++ 5 files changed, 274 insertions(+), 107 deletions(-) create mode 100755 tv/cec/1.0/default/HdmiCecPort.cpp create mode 100755 tv/cec/1.0/default/HdmiCecPort.h diff --git a/tv/cec/1.0/default/Android.bp b/tv/cec/1.0/default/Android.bp index b4053dfe1d..e4c226d6f6 100644 --- a/tv/cec/1.0/default/Android.bp +++ b/tv/cec/1.0/default/Android.bp @@ -15,6 +15,7 @@ cc_library_shared { srcs: [ "HdmiCec.cpp", "HdmiCecDefault.cpp", + "HdmiCecPort.cpp", ], shared_libs: [ diff --git a/tv/cec/1.0/default/HdmiCecDefault.cpp b/tv/cec/1.0/default/HdmiCecDefault.cpp index ad89312e8c..26ccb7d42e 100644 --- a/tv/cec/1.0/default/HdmiCecDefault.cpp +++ b/tv/cec/1.0/default/HdmiCecDefault.cpp @@ -19,15 +19,19 @@ #include #include +#include #include #include #include #include -#include -#include #include "HdmiCecDefault.h" +#define PROPERTY_DEVICE_TYPE "ro.hdmi.device_type" +#define MIN_PORT_ID 0 +#define MAX_PORT_ID 15 +#define INVALID_PHYSICAL_ADDRESS 0xFFFF + namespace android { namespace hardware { namespace tv { @@ -36,10 +40,10 @@ namespace V1_0 { namespace implementation { using android::base::GetUintProperty; +using std::stoi; +using std::string; HdmiCecDefault::HdmiCecDefault() { - mCecFd = -1; - mExitFd = -1; mCecEnabled = false; mWakeupEnabled = false; mCecControlEnabled = false; @@ -58,7 +62,7 @@ Return HdmiCecDefault::addLogicalAddress(CecLogicalAddress addr) { } cec_log_addrs cecLogAddrs; - int ret = ioctl(mCecFd, CEC_ADAP_G_LOG_ADDRS, &cecLogAddrs); + int ret = ioctl(mHdmiCecPorts[MIN_PORT_ID]->mCecFd, CEC_ADAP_G_LOG_ADDRS, &cecLogAddrs); if (ret) { LOG(ERROR) << "Add logical address failed, Error = " << strerror(errno); return Result::FAILURE_BUSY; @@ -124,27 +128,36 @@ Return HdmiCecDefault::addLogicalAddress(CecLogicalAddress addr) { cecLogAddrs.features[logAddrIndex][0] = 0; cecLogAddrs.features[logAddrIndex][1] = 0; - ret = ioctl(mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs); - if (ret) { - LOG(ERROR) << "Add logical address failed, Error = " << strerror(errno); - return Result::FAILURE_BUSY; + // Return failure only if add logical address fails for all the ports + Return result = Result::FAILURE_BUSY; + for (int i = 0; i < mHdmiCecPorts.size(); i++) { + ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs); + if (ret) { + LOG(ERROR) << "Add logical address failed for port " << mHdmiCecPorts[i]->mPortId + << ", Error = " << strerror(errno); + } else { + result = Result::SUCCESS; + } } - return Result::SUCCESS; + return result; } Return HdmiCecDefault::clearLogicalAddress() { cec_log_addrs cecLogAddrs; memset(&cecLogAddrs, 0, sizeof(cecLogAddrs)); - int ret = ioctl(mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs); - if (ret) { - LOG(ERROR) << "Clear logical Address failed, Error = " << strerror(errno); + for (int i = 0; i < mHdmiCecPorts.size(); i++) { + int ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs); + if (ret) { + LOG(ERROR) << "Clear logical Address failed for port " << mHdmiCecPorts[i]->mPortId + << ", Error = " << strerror(errno); + } } return Void(); } Return HdmiCecDefault::getPhysicalAddress(getPhysicalAddress_cb callback) { uint16_t addr; - int ret = ioctl(mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr); + int ret = ioctl(mHdmiCecPorts[MIN_PORT_ID]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr); if (ret) { LOG(ERROR) << "Get physical address failed, Error = " << strerror(errno); callback(Result::FAILURE_INVALID_STATE, addr); @@ -171,27 +184,25 @@ Return HdmiCecDefault::sendMessage(const CecMessage& message) } cecMsg.len = message.body.size() + 1; - int ret = ioctl(mCecFd, CEC_TRANSMIT, &cecMsg); + // Return failure only if send message fails for all the ports + Return result = SendMessageResult::FAIL; + for (int i = 0; i < mHdmiCecPorts.size(); i++) { + int ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_TRANSMIT, &cecMsg); - if (ret) { - LOG(ERROR) << "Send message failed, Error = " << strerror(errno); - return SendMessageResult::FAIL; - } + if (ret) { + LOG(ERROR) << "Send message failed, Error = " << strerror(errno); + continue; + } - if (cecMsg.tx_status != CEC_TX_STATUS_OK) { - LOG(ERROR) << "Send message tx_status = " << cecMsg.tx_status; - } + if (cecMsg.tx_status != CEC_TX_STATUS_OK) { + LOG(ERROR) << "Send message tx_status = " << cecMsg.tx_status; + } - switch (cecMsg.tx_status) { - case CEC_TX_STATUS_OK: - return SendMessageResult::SUCCESS; - case CEC_TX_STATUS_ARB_LOST: - return SendMessageResult::BUSY; - case CEC_TX_STATUS_NACK: - return SendMessageResult::NACK; - default: - return SendMessageResult::FAIL; + if (result != SendMessageResult::SUCCESS) { + result = getSendMessageResult(cecMsg.tx_status); + } } + return result; } Return HdmiCecDefault::setCallback(const sp& callback) { @@ -216,19 +227,25 @@ Return HdmiCecDefault::getVendorId() { } Return HdmiCecDefault::getPortInfo(getPortInfo_cb callback) { - uint16_t addr; - int ret = ioctl(mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr); - if (ret) { - LOG(ERROR) << "Get port info failed, Error = " << strerror(errno); + hidl_vec portInfos(mHdmiCecPorts.size()); + for (int i = 0; i < mHdmiCecPorts.size(); i++) { + uint16_t addr = INVALID_PHYSICAL_ADDRESS; + int ret = ioctl(mHdmiCecPorts[i]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr); + if (ret) { + LOG(ERROR) << "Get port info failed for port : " << mHdmiCecPorts[i]->mPortId + << ", Error = " << strerror(errno); + } + HdmiPortType type = HdmiPortType::INPUT; + uint32_t deviceType = GetUintProperty(PROPERTY_DEVICE_TYPE, CEC_DEVICE_PLAYBACK); + if (deviceType != CEC_DEVICE_TV && i == MIN_PORT_ID) { + type = HdmiPortType::OUTPUT; + } + portInfos[i] = {.type = type, + .portId = mHdmiCecPorts[i]->mPortId, + .cecSupported = true, + .arcSupported = false, + .physicalAddress = addr}; } - - uint32_t type = GetUintProperty("ro.hdmi.device_type", CEC_DEVICE_PLAYBACK); - hidl_vec portInfos(1); - portInfos[0] = {.type = (type == CEC_DEVICE_TV ? HdmiPortType::INPUT : HdmiPortType::OUTPUT), - .portId = 1, - .cecSupported = true, - .arcSupported = false, - .physicalAddress = addr}; callback(portInfos); return Void(); } @@ -259,9 +276,9 @@ Return HdmiCecDefault::enableAudioReturnChannel(int32_t /*portId*/, bool / return Void(); } -Return HdmiCecDefault::isConnected(int32_t /*portId*/) { +Return HdmiCecDefault::isConnected(int32_t portId) { uint16_t addr; - int ret = ioctl(mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr); + int ret = ioctl(mHdmiCecPorts[portId]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr); if (ret) { LOG(ERROR) << "Is connected failed, Error = " << strerror(errno); return false; @@ -272,46 +289,44 @@ Return HdmiCecDefault::isConnected(int32_t /*portId*/) { return true; } -// Initialise the cec file descriptor +int getPortId(string cecFilename) { + int portId = stoi(cecFilename.substr(3)); + if (portId >= MIN_PORT_ID && portId <= MAX_PORT_ID) { + return portId; + } else { + return -1; + } +} + +// Initialise the cec file descriptors Return HdmiCecDefault::init() { - const char* path = "/dev/cec0"; - mCecFd = open(path, O_RDWR); - if (mCecFd < 0) { - LOG(ERROR) << "Failed to open " << path << ", Error = " << strerror(errno); - return Result::FAILURE_NOT_SUPPORTED; - } - mExitFd = eventfd(0, EFD_NONBLOCK); - if (mExitFd < 0) { - LOG(ERROR) << "Failed to open eventfd, Error = " << strerror(errno); - release(); - return Result::FAILURE_NOT_SUPPORTED; + const char* parentPath = "/dev/"; + DIR* dir = opendir(parentPath); + const char* cecFilename = "cec"; + + while (struct dirent* dirEntry = readdir(dir)) { + string filename = dirEntry->d_name; + if (filename.compare(0, 3, cecFilename, 0, 3) == 0) { + int portId = getPortId(filename); + if (portId == -1) { + continue; + } + shared_ptr hdmiCecPort(new HdmiCecPort(portId)); + string filepath = parentPath + filename; + Result result = hdmiCecPort->init(filepath.c_str()); + if (result != Result::SUCCESS) { + continue; + } + thread eventThread(&HdmiCecDefault::event_thread, this, hdmiCecPort.get()); + mEventThreads.push_back(std::move(eventThread)); + mHdmiCecPorts.push_back(std::move(hdmiCecPort)); + } } - // Ensure the CEC device supports required capabilities - cec_caps caps = {}; - int ret = ioctl(mCecFd, CEC_ADAP_G_CAPS, &caps); - if (ret) { - LOG(ERROR) << "Unable to query cec adapter capabilities, Error = " << strerror(errno); - release(); + if (mHdmiCecPorts.empty()) { return Result::FAILURE_NOT_SUPPORTED; } - if (!(caps.capabilities & (CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH))) { - LOG(ERROR) << "Wrong cec adapter capabilities " << caps.capabilities; - release(); - return Result::FAILURE_NOT_SUPPORTED; - } - - uint32_t mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU; - ret = ioctl(mCecFd, CEC_S_MODE, &mode); - if (ret) { - LOG(ERROR) << "Unable to set initiator mode, Error = " << strerror(errno); - release(); - return Result::FAILURE_NOT_SUPPORTED; - } - - mEventThread = thread(&HdmiCecDefault::event_thread, this); - mCecEnabled = true; mWakeupEnabled = true; mCecControlEnabled = true; @@ -319,31 +334,25 @@ Return HdmiCecDefault::init() { } Return HdmiCecDefault::release() { - if (mExitFd > 0) { - uint64_t tmp = 1; - write(mExitFd, &tmp, sizeof(tmp)); - if (mEventThread.joinable()) { - mEventThread.join(); - } - } - if (mExitFd > 0) { - close(mExitFd); - } - if (mCecFd > 0) { - close(mCecFd); - } mCecEnabled = false; mWakeupEnabled = false; mCecControlEnabled = false; + for (thread& eventThread : mEventThreads) { + if (eventThread.joinable()) { + eventThread.join(); + } + } setCallback(nullptr); + mHdmiCecPorts.clear(); + mEventThreads.clear(); return Void(); } -void HdmiCecDefault::event_thread() { - pollfd ufds[3] = { - {mCecFd, POLLIN, 0}, - {mCecFd, POLLERR, 0}, - {mExitFd, POLLIN, 0}, +void HdmiCecDefault::event_thread(HdmiCecPort* hdmiCecPort) { + struct pollfd ufds[3] = { + {hdmiCecPort->mCecFd, POLLIN, 0}, + {hdmiCecPort->mCecFd, POLLERR, 0}, + {hdmiCecPort->mExitFd, POLLIN, 0}, }; while (1) { @@ -363,7 +372,7 @@ void HdmiCecDefault::event_thread() { if (ufds[1].revents == POLLERR) { /* CEC Event */ cec_event ev; - ret = ioctl(mCecFd, CEC_DQEVENT, &ev); + ret = ioctl(hdmiCecPort->mCecFd, CEC_DQEVENT, &ev); if (ret) { LOG(ERROR) << "CEC_DQEVENT failed, Error = " << strerror(errno); @@ -378,7 +387,7 @@ void HdmiCecDefault::event_thread() { if (mCallback != nullptr) { HotplugEvent hotplugEvent{ .connected = (ev.state_change.phys_addr != CEC_PHYS_ADDR_INVALID), - .portId = 1}; + .portId = hdmiCecPort->mPortId}; mCallback->onHotplugEvent(hotplugEvent); } else { LOG(ERROR) << "No event callback for hotplug"; @@ -388,7 +397,7 @@ void HdmiCecDefault::event_thread() { if (ufds[0].revents == POLLIN) { /* CEC Driver */ cec_msg msg = {}; - ret = ioctl(mCecFd, CEC_RECEIVE, &msg); + ret = ioctl(hdmiCecPort->mCecFd, CEC_RECEIVE, &msg); if (ret) { LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno); @@ -489,6 +498,19 @@ bool HdmiCecDefault::isPowerUICommand(cec_msg message) { return false; } } + +Return HdmiCecDefault::getSendMessageResult(int tx_status) { + switch (tx_status) { + case CEC_TX_STATUS_OK: + return SendMessageResult::SUCCESS; + case CEC_TX_STATUS_ARB_LOST: + return SendMessageResult::BUSY; + case CEC_TX_STATUS_NACK: + return SendMessageResult::NACK; + default: + return SendMessageResult::FAIL; + } +} } // namespace implementation } // namespace V1_0 } // namespace cec diff --git a/tv/cec/1.0/default/HdmiCecDefault.h b/tv/cec/1.0/default/HdmiCecDefault.h index 81229d32ce..6574429a6d 100644 --- a/tv/cec/1.0/default/HdmiCecDefault.h +++ b/tv/cec/1.0/default/HdmiCecDefault.h @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#include #include #include #include +#include +#include "HdmiCecPort.h" namespace android { namespace hardware { @@ -26,7 +26,9 @@ namespace cec { namespace V1_0 { namespace implementation { +using std::shared_ptr; using std::thread; +using std::vector; class HdmiCecDefault : public IHdmiCec, public hidl_death_recipient { public: @@ -54,14 +56,16 @@ class HdmiCecDefault : public IHdmiCec, public hidl_death_recipient { Return release(); private: - void event_thread(); + void event_thread(HdmiCecPort* hdmiCecPort); static int getOpcode(cec_msg message); static int getFirstParam(cec_msg message); static bool isWakeupMessage(cec_msg message); static bool isTransferableInSleep(cec_msg message); static bool isPowerUICommand(cec_msg message); + static Return getSendMessageResult(int tx_status); - thread mEventThread; + vector mEventThreads; + vector> mHdmiCecPorts; // When set to false, all the CEC commands are discarded. True by default after initialization. bool mCecEnabled; @@ -78,9 +82,6 @@ class HdmiCecDefault : public IHdmiCec, public hidl_death_recipient { */ bool mCecControlEnabled; sp mCallback; - - int mCecFd; - int mExitFd; }; } // namespace implementation } // namespace V1_0 diff --git a/tv/cec/1.0/default/HdmiCecPort.cpp b/tv/cec/1.0/default/HdmiCecPort.cpp new file mode 100755 index 0000000000..73dda127f9 --- /dev/null +++ b/tv/cec/1.0/default/HdmiCecPort.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 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.cec@1.0-impl" + +#include +#include +#include +#include +#include +#include + +#include "HdmiCecPort.h" + +namespace android { +namespace hardware { +namespace tv { +namespace cec { +namespace V1_0 { +namespace implementation { + +HdmiCecPort::HdmiCecPort(unsigned int portId) { + mPortId = portId; + mCecFd = -1; + mExitFd = -1; +} + +HdmiCecPort::~HdmiCecPort() { + release(); +} + +// Initialise the cec file descriptor +Return HdmiCecPort::init(const char* path) { + mCecFd = open(path, O_RDWR); + if (mCecFd < 0) { + LOG(ERROR) << "Failed to open " << path << ", Error = " << strerror(errno); + return Result::FAILURE_NOT_SUPPORTED; + } + mExitFd = eventfd(0, EFD_NONBLOCK); + if (mExitFd < 0) { + LOG(ERROR) << "Failed to open eventfd, Error = " << strerror(errno); + release(); + return Result::FAILURE_NOT_SUPPORTED; + } + + // Ensure the CEC device supports required capabilities + struct cec_caps caps = {}; + int ret = ioctl(mCecFd, CEC_ADAP_G_CAPS, &caps); + if (ret) { + LOG(ERROR) << "Unable to query cec adapter capabilities, Error = " << strerror(errno); + release(); + return Result::FAILURE_NOT_SUPPORTED; + } + + if (!(caps.capabilities & (CEC_CAP_LOG_ADDRS | CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH))) { + LOG(ERROR) << "Wrong cec adapter capabilities " << caps.capabilities; + release(); + return Result::FAILURE_NOT_SUPPORTED; + } + + uint32_t mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU; + ret = ioctl(mCecFd, CEC_S_MODE, &mode); + if (ret) { + LOG(ERROR) << "Unable to set initiator mode, Error = " << strerror(errno); + release(); + return Result::FAILURE_NOT_SUPPORTED; + } + return Result::SUCCESS; +} + +Return HdmiCecPort::release() { + if (mExitFd > 0) { + uint64_t tmp = 1; + write(mExitFd, &tmp, sizeof(tmp)); + } + if (mExitFd > 0) { + close(mExitFd); + } + if (mCecFd > 0) { + close(mCecFd); + } + return Void(); +} +} // namespace implementation +} // namespace V1_0 +} // namespace cec +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/cec/1.0/default/HdmiCecPort.h b/tv/cec/1.0/default/HdmiCecPort.h new file mode 100755 index 0000000000..2a2fdef0a8 --- /dev/null +++ b/tv/cec/1.0/default/HdmiCecPort.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 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. + */ +#include + +namespace android { +namespace hardware { +namespace tv { +namespace cec { +namespace V1_0 { +namespace implementation { + +class HdmiCecPort { + public: + HdmiCecPort(unsigned int portId); + ~HdmiCecPort(); + Return init(const char* path); + Return release(); + + unsigned int mPortId; + int mCecFd; + int mExitFd; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace cec +} // namespace tv +} // namespace hardware +} // namespace android