Merge "CEC: Add support for multiple ports to default HdmiCec" am: 63a8152f8e am: bfd12e9bf5

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1773889

Change-Id: I66714a05753aeea2f2fcc17c5605f387c6662947
This commit is contained in:
Nathalie Le Clair
2021-11-02 17:48:22 +00:00
committed by Automerger Merge Worker
5 changed files with 274 additions and 107 deletions

View File

@@ -15,6 +15,7 @@ cc_library_shared {
srcs: [
"HdmiCec.cpp",
"HdmiCecDefault.cpp",
"HdmiCecPort.cpp",
],
shared_libs: [

View File

@@ -19,15 +19,19 @@
#include <android-base/properties.h>
#include <cutils/properties.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include <poll.h>
#include <sys/eventfd.h>
#include <algorithm>
#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<Result> 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<Result> 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 = 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<void> 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<void> 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<SendMessageResult> 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<SendMessageResult> 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<void> HdmiCecDefault::setCallback(const sp<IHdmiCecCallback>& callback) {
@@ -216,19 +227,25 @@ Return<uint32_t> HdmiCecDefault::getVendorId() {
}
Return<void> 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<HdmiPortInfo> 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<uint32_t>(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<uint32_t>("ro.hdmi.device_type", CEC_DEVICE_PLAYBACK);
hidl_vec<HdmiPortInfo> 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<void> HdmiCecDefault::enableAudioReturnChannel(int32_t /*portId*/, bool /
return Void();
}
Return<bool> HdmiCecDefault::isConnected(int32_t /*portId*/) {
Return<bool> 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<bool> 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<Result> 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> 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<Result> HdmiCecDefault::init() {
}
Return<void> 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<SendMessageResult> 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

View File

@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android/hardware/tv/cec/1.0/IHdmiCec.h>
#include <hardware/hdmi_cec.h>
#include <linux/cec.h>
#include <thread>
#include <vector>
#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<void> 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<SendMessageResult> getSendMessageResult(int tx_status);
thread mEventThread;
vector<thread> mEventThreads;
vector<shared_ptr<HdmiCecPort>> 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<IHdmiCecCallback> mCallback;
int mCecFd;
int mExitFd;
};
} // namespace implementation
} // namespace V1_0

View File

@@ -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 <android-base/logging.h>
#include <errno.h>
#include <linux/cec.h>
#include <linux/ioctl.h>
#include <sys/eventfd.h>
#include <algorithm>
#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<Result> 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<void> 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

View File

@@ -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 <android/hardware/tv/cec/1.0/IHdmiCec.h>
namespace android {
namespace hardware {
namespace tv {
namespace cec {
namespace V1_0 {
namespace implementation {
class HdmiCecPort {
public:
HdmiCecPort(unsigned int portId);
~HdmiCecPort();
Return<Result> init(const char* path);
Return<void> release();
unsigned int mPortId;
int mCecFd;
int mExitFd;
};
} // namespace implementation
} // namespace V1_0
} // namespace cec
} // namespace tv
} // namespace hardware
} // namespace android