Enable HDMI CEC and add HDMI hotplug monitoring
Install the HDMI CEC and HDMI connection APEX packages plus the android.hardware.hdmi.cec.xml feature file, set the playback-device CEC properties, and give system ownership of /dev/cec0 in ueventd. The connection HAL now reports a single HDMI output port and monitors /sys/class/drm/card0-HDMI-A-1/status with poll(), reporting hotplug transitions through the AIDL callback and shutting the monitor thread down through an eventfd. The CEC HAL now polls POLLPRI for CEC_DQEVENT and POLLIN for CEC_RECEIVE, guards the ioctl paths against an uninitialised adapter, and bounds message length by CEC_MAX_MSG_SIZE.
This commit is contained in:
parent
04e94e9938
commit
8b7cdd6f38
6 changed files with 245 additions and 100 deletions
14
device.mk
14
device.mk
|
|
@ -113,18 +113,14 @@ PRODUCT_COPY_FILES += \
|
|||
# PRODUCT_COPY_FILES += \
|
||||
# $(DEVICE_PATH)/camera/media_profiles_V1_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.xml
|
||||
|
||||
# # CEC
|
||||
# PRODUCT_PACKAGES += \
|
||||
# com.android.hardware.tv.hdmi.cec.opi5 \
|
||||
# com.android.hardware.tv.hdmi.connection.opi5
|
||||
|
||||
# PRODUCT_COPY_FILES += \
|
||||
# frameworks/native/data/etc/android.hardware.hdmi.cec.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.hdmi.cec.xml
|
||||
|
||||
#cec
|
||||
# CEC
|
||||
PRODUCT_PACKAGES += \
|
||||
com.android.hardware.tv.hdmi.cec.opi5 \
|
||||
com.android.hardware.tv.hdmi.connection.opi5
|
||||
|
||||
PRODUCT_COPY_FILES += \
|
||||
frameworks/native/data/etc/android.hardware.hdmi.cec.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/permissions/android.hardware.hdmi.cec.xml
|
||||
|
||||
# Debugfs
|
||||
PRODUCT_SET_DEBUGFS_RESTRICTIONS := false
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ ScopedAStatus HdmiCec::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_r
|
|||
*_aidl_return = Result::FAILURE_INVALID_ARGS;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
if (mHdmiCecPorts.empty()) {
|
||||
*_aidl_return = Result::FAILURE_INVALID_STATE;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
cec_log_addrs cecLogAddrs;
|
||||
int ret = ioctl(mHdmiCecPorts[0]->mCecFd, CEC_ADAP_G_LOG_ADDRS, &cecLogAddrs);
|
||||
|
|
@ -72,7 +76,6 @@ ScopedAStatus HdmiCec::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_r
|
|||
*_aidl_return = Result::FAILURE_BUSY;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
cecLogAddrs.cec_version = CEC_OP_CEC_VERSION_1_4;
|
||||
cecLogAddrs.vendor_id = 0x000c03; // HDMI LLC vendor ID
|
||||
|
||||
|
|
@ -148,15 +151,18 @@ ScopedAStatus HdmiCec::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_r
|
|||
}
|
||||
|
||||
ScopedAStatus HdmiCec::clearLogicalAddress() {
|
||||
if (mHdmiCecPorts.empty()) {
|
||||
LOG(ERROR) << "Clear logical address failed: no CEC adapter";
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
cec_log_addrs cecLogAddrs;
|
||||
memset(&cecLogAddrs, 0, sizeof(cecLogAddrs));
|
||||
|
||||
int ret = ioctl(mHdmiCecPorts[0]->mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs);
|
||||
if (ret) {
|
||||
LOG(ERROR) << "Clear logical Address failed for port " << mHdmiCecPorts[0]->mPortId
|
||||
<< ", Error = " << strerror(errno);
|
||||
}
|
||||
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
|
|
@ -170,8 +176,12 @@ ScopedAStatus HdmiCec::getCecVersion(int32_t* _aidl_return) {
|
|||
}
|
||||
|
||||
ScopedAStatus HdmiCec::getPhysicalAddress(int32_t* _aidl_return) {
|
||||
uint16_t addr;
|
||||
if (mHdmiCecPorts.empty()) {
|
||||
return ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::FAILURE_INVALID_STATE));
|
||||
}
|
||||
|
||||
uint16_t addr;
|
||||
int ret = ioctl(mHdmiCecPorts[0]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
|
||||
if (ret) {
|
||||
LOG(ERROR) << "Get physical address failed, Error = " << strerror(errno);
|
||||
|
|
@ -193,6 +203,10 @@ ScopedAStatus HdmiCec::sendMessage(const CecMessage& message, SendMessageResult*
|
|||
*_aidl_return = SendMessageResult::FAIL;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
if (mHdmiCecPorts.empty() || message.body.size() + 1 > CEC_MAX_MSG_SIZE) {
|
||||
*_aidl_return = SendMessageResult::FAIL;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
cec_msg cecMsg;
|
||||
memset(&cecMsg, 0, sizeof(cec_msg));
|
||||
|
|
@ -297,50 +311,51 @@ void HdmiCec::release() {
|
|||
}
|
||||
|
||||
void HdmiCec::event_thread(HdmiCecPort* hdmiCecPort) {
|
||||
struct pollfd ufds[3] = {
|
||||
{hdmiCecPort->mCecFd, POLLIN, 0},
|
||||
{hdmiCecPort->mCecFd, POLLERR, 0},
|
||||
struct pollfd ufds[2] = {
|
||||
{hdmiCecPort->mCecFd, POLLIN | POLLPRI, 0},
|
||||
{hdmiCecPort->mExitFd, POLLIN, 0},
|
||||
};
|
||||
|
||||
while (1) {
|
||||
ufds[0].revents = 0;
|
||||
ufds[1].revents = 0;
|
||||
ufds[2].revents = 0;
|
||||
|
||||
int ret = poll(ufds, /* size(ufds) = */ 3, /* timeout = */ -1);
|
||||
|
||||
int ret = poll(ufds, 2, -1);
|
||||
if (ret <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ufds[2].revents == POLLIN) { /* Exit */
|
||||
if (ufds[1].revents & POLLIN) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ufds[1].revents == POLLERR) { /* CEC Event */
|
||||
if (ufds[0].revents & POLLPRI) {
|
||||
cec_event ev;
|
||||
ret = ioctl(hdmiCecPort->mCecFd, CEC_DQEVENT, &ev);
|
||||
|
||||
if (ret) {
|
||||
LOG(ERROR) << "CEC_DQEVENT failed, Error = " << strerror(errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mCecEnabled) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (ufds[0].revents == POLLIN) { /* CEC Driver */
|
||||
if (!(ufds[0].revents & POLLIN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cec_msg msg = {};
|
||||
ret = ioctl(hdmiCecPort->mCecFd, CEC_RECEIVE, &msg);
|
||||
|
||||
if (ret) {
|
||||
LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg.len == 0 || msg.len > CEC_MAX_MSG_SIZE) {
|
||||
LOG(ERROR) << "Invalid CEC message length: " << msg.len;
|
||||
continue;
|
||||
}
|
||||
if (msg.rx_status != CEC_RX_STATUS_OK) {
|
||||
LOG(ERROR) << "msg rx_status = " << msg.rx_status;
|
||||
continue;
|
||||
|
|
@ -361,7 +376,8 @@ void HdmiCec::event_thread(HdmiCecPort* hdmiCecPort) {
|
|||
}
|
||||
|
||||
if (mCallback != nullptr) {
|
||||
size_t length = std::min(msg.len - 1, (uint32_t)(CEC_MESSAGE_BODY_MAX_LENGTH - 1));
|
||||
size_t length = std::min(static_cast<size_t>(msg.len - 1),
|
||||
static_cast<size_t>(CEC_MESSAGE_BODY_MAX_LENGTH - 1));
|
||||
CecMessage cecMessage{
|
||||
.initiator = static_cast<CecLogicalAddress>(msg.msg[0] >> 4),
|
||||
.destination = static_cast<CecLogicalAddress>(msg.msg[0] & 0xf),
|
||||
|
|
@ -376,7 +392,6 @@ void HdmiCec::event_thread(HdmiCecPort* hdmiCecPort) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int HdmiCec::getOpcode(cec_msg message) {
|
||||
return static_cast<uint8_t>(message.msg[1]);
|
||||
|
|
|
|||
|
|
@ -19,12 +19,16 @@
|
|||
|
||||
#include "HdmiConnection.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
|
||||
using android::base::ReadFileToString;
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using ndk::ScopedAStatus;
|
||||
using std::string;
|
||||
|
||||
namespace android {
|
||||
namespace hardware {
|
||||
|
|
@ -33,26 +37,49 @@ namespace hdmi {
|
|||
namespace connection {
|
||||
namespace implementation {
|
||||
|
||||
static const string drmCard = "card0";
|
||||
static constexpr char kHdmiStatusPath[] = "/sys/class/drm/card0-HDMI-A-1/status";
|
||||
|
||||
static bool readStatus(int fd, bool* connected) {
|
||||
char status[32] = {};
|
||||
if (lseek(fd, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t length = read(fd, status, sizeof(status) - 1);
|
||||
if (length < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*connected = length >= 9 && !strncmp(status, "connected", 9);
|
||||
return true;
|
||||
}
|
||||
|
||||
HdmiConnection::HdmiConnection() {
|
||||
mCallback = nullptr;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
mPortInfos.push_back(
|
||||
{.type = HdmiPortType::OUTPUT,
|
||||
.portId = i,
|
||||
mPortInfos.push_back({.type = HdmiPortType::OUTPUT,
|
||||
.portId = 1,
|
||||
.cecSupported = true,
|
||||
.arcSupported = false,
|
||||
.eArcSupported = false,
|
||||
.physicalAddress = 0xFFFF});
|
||||
mHpdSignal.push_back(HpdSignal::HDMI_HPD_PHYSICAL);
|
||||
|
||||
mStopFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
||||
if (mStopFd < 0) {
|
||||
PLOG(ERROR) << "Failed to create HDMI monitor eventfd";
|
||||
return;
|
||||
}
|
||||
|
||||
mMonitorThread = std::thread(&HdmiConnection::monitorLoop, this);
|
||||
}
|
||||
|
||||
HdmiConnection::~HdmiConnection() {
|
||||
if (mCallback != nullptr) {
|
||||
mCallback = nullptr;
|
||||
signalMonitorStop();
|
||||
if (mMonitorThread.joinable()) {
|
||||
mMonitorThread.join();
|
||||
}
|
||||
if (mStopFd >= 0) {
|
||||
close(mStopFd);
|
||||
mStopFd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,55 +89,152 @@ ScopedAStatus HdmiConnection::getPortInfo(std::vector<HdmiPortInfo>* _aidl_retur
|
|||
}
|
||||
|
||||
ScopedAStatus HdmiConnection::isConnected(int32_t portId, bool* _aidl_return) {
|
||||
if (portId != 0 && portId != 1) {
|
||||
if (portId != 1) {
|
||||
*_aidl_return = false;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
int fd = open(kHdmiStatusPath, O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
*_aidl_return = false;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
bool connected = false;
|
||||
string hdmiStatusPath = "/sys/class/drm/" + drmCard + "-HDMI-A-" + to_string(portId + 1) + "/status";
|
||||
if (!access(hdmiStatusPath.c_str(), R_OK)) {
|
||||
string connectedValue;
|
||||
if (ReadFileToString(hdmiStatusPath, &connectedValue)) {
|
||||
connected = !connectedValue.compare("connected\n");
|
||||
if (!readStatus(fd, &connected)) {
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "portId: " << portId << ", connected: " << connected;
|
||||
close(fd);
|
||||
|
||||
LOG(INFO) << "portId: " << portId << ", connected: " << connected;
|
||||
*_aidl_return = connected;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ScopedAStatus HdmiConnection::setCallback(const std::shared_ptr<IHdmiConnectionCallback>& callback) {
|
||||
if (mCallback != nullptr) {
|
||||
mCallback = nullptr;
|
||||
}
|
||||
|
||||
if (callback != nullptr) {
|
||||
ScopedAStatus HdmiConnection::setCallback(
|
||||
const std::shared_ptr<IHdmiConnectionCallback>& callback) {
|
||||
std::lock_guard<std::mutex> lock(mCallbackMutex);
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ScopedAStatus HdmiConnection::setHpdSignal(HpdSignal signal, int32_t portId) {
|
||||
if (portId != 0 && portId != 1) {
|
||||
if (portId != 1) {
|
||||
return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
|
||||
mHpdSignal.at(portId) = signal;
|
||||
mHpdSignal[0] = signal;
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ScopedAStatus HdmiConnection::getHpdSignal(int32_t portId, HpdSignal* _aidl_return) {
|
||||
if (portId != 0 && portId != 1) {
|
||||
if (portId != 1) {
|
||||
return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
|
||||
*_aidl_return = mHpdSignal.at(portId);
|
||||
*_aidl_return = mHpdSignal[0];
|
||||
return ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
void HdmiConnection::signalMonitorStop() {
|
||||
if (mStopFd < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t value = 1;
|
||||
if (write(mStopFd, &value, sizeof(value)) < 0 && errno != EAGAIN) {
|
||||
PLOG(ERROR) << "Failed to stop HDMI monitor";
|
||||
}
|
||||
}
|
||||
|
||||
void HdmiConnection::monitorLoop() {
|
||||
int statusFd = -1;
|
||||
bool haveStatus = false;
|
||||
bool lastConnected = false;
|
||||
bool openErrorReported = false;
|
||||
|
||||
while (true) {
|
||||
if (statusFd < 0) {
|
||||
pollfd stopPoll = {.fd = mStopFd, .events = POLLIN, .revents = 0};
|
||||
int ret = poll(&stopPoll, 1, 1000);
|
||||
if (ret < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (ret > 0 && (stopPoll.revents & POLLIN)) {
|
||||
break;
|
||||
}
|
||||
if (ret == 0) {
|
||||
statusFd = open(kHdmiStatusPath, O_RDONLY | O_CLOEXEC);
|
||||
if (statusFd >= 0) {
|
||||
haveStatus = false;
|
||||
openErrorReported = false;
|
||||
} else if (!openErrorReported) {
|
||||
PLOG(ERROR) << "Failed to open HDMI status file " << kHdmiStatusPath;
|
||||
openErrorReported = true;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!haveStatus && !readStatus(statusFd, &lastConnected)) {
|
||||
close(statusFd);
|
||||
statusFd = -1;
|
||||
continue;
|
||||
}
|
||||
haveStatus = true;
|
||||
|
||||
pollfd fds[2] = {
|
||||
{.fd = statusFd, .events = POLLPRI | POLLERR, .revents = 0},
|
||||
{.fd = mStopFd, .events = POLLIN, .revents = 0},
|
||||
};
|
||||
int ret = poll(fds, 2, 1000);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
close(statusFd);
|
||||
statusFd = -1;
|
||||
haveStatus = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLIN) {
|
||||
break;
|
||||
}
|
||||
if (ret == 0 || !(fds[0].revents & (POLLPRI | POLLERR))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool connected = false;
|
||||
if (!readStatus(statusFd, &connected)) {
|
||||
close(statusFd);
|
||||
statusFd = -1;
|
||||
haveStatus = false;
|
||||
continue;
|
||||
}
|
||||
if (connected == lastConnected) {
|
||||
continue;
|
||||
}
|
||||
lastConnected = connected;
|
||||
|
||||
std::shared_ptr<IHdmiConnectionCallback> callback;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mCallbackMutex);
|
||||
callback = mCallback;
|
||||
}
|
||||
if (callback != nullptr) {
|
||||
ScopedAStatus status = callback->onHotplugEvent(connected, 1);
|
||||
if (!status.isOk()) {
|
||||
LOG(ERROR) << "HDMI hotplug callback failed";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (statusFd >= 0) {
|
||||
close(statusFd);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace connection
|
||||
} // namespace hdmi
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@
|
|||
#include <aidl/android/hardware/tv/hdmi/connection/BnHdmiConnection.h>
|
||||
#include <aidl/android/hardware/tv/hdmi/connection/Result.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace android {
|
||||
|
|
@ -48,11 +50,16 @@ struct HdmiConnection : public BnHdmiConnection {
|
|||
::ndk::ScopedAStatus getHpdSignal(int32_t portId, HpdSignal* _aidl_return) override;
|
||||
|
||||
private:
|
||||
void monitorLoop();
|
||||
void signalMonitorStop();
|
||||
|
||||
std::vector<HdmiPortInfo> mPortInfos;
|
||||
std::vector<HpdSignal> mHpdSignal;
|
||||
|
||||
std::mutex mCallbackMutex;
|
||||
std::shared_ptr<IHdmiConnectionCallback> mCallback;
|
||||
};
|
||||
std::thread mMonitorThread;
|
||||
int mStopFd = -1;
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace connection
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
/dev/v4l-subdev* 0660 system camera
|
||||
/dev/video* 0660 system camera
|
||||
|
||||
# HDMI CEC
|
||||
/dev/cec0 0660 system system
|
||||
|
||||
# DMA
|
||||
/dev/dma_heap/linux,cma 0666 system graphics
|
||||
/dev/dma_heap/system 0666 system graphics
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ persist.bluetooth.a2dp_aac.vbr_supported=true
|
|||
ro.hardware.camera=libcamera
|
||||
|
||||
# CEC
|
||||
#persist.vendor.hdmi.cec_device=cec0
|
||||
#ro.hdmi.cec_device_types=playback_device
|
||||
#ro.hdmi.device_type=4
|
||||
persist.vendor.hdmi.cec_device=cec0
|
||||
ro.hdmi.cec_device_types=playback_device
|
||||
ro.hdmi.device_type=4
|
||||
|
||||
# Chipset
|
||||
ro.soc.manufacturer=Rockchip
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue