1
0
Fork 0

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:
Brendan Szymanski 2026-08-09 22:18:52 -04:00
parent 04e94e9938
commit 8b7cdd6f38
6 changed files with 245 additions and 100 deletions

View file

@ -113,18 +113,14 @@ PRODUCT_COPY_FILES += \
# PRODUCT_COPY_FILES += \ # PRODUCT_COPY_FILES += \
# $(DEVICE_PATH)/camera/media_profiles_V1_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.xml # $(DEVICE_PATH)/camera/media_profiles_V1_0.xml:$(TARGET_COPY_OUT_VENDOR)/etc/media_profiles_V1_0.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_VENDOR)/etc/permissions/android.hardware.hdmi.cec.xml
#cec
PRODUCT_PACKAGES += \ PRODUCT_PACKAGES += \
com.android.hardware.tv.hdmi.cec.opi5 \
com.android.hardware.tv.hdmi.connection.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 # Debugfs
PRODUCT_SET_DEBUGFS_RESTRICTIONS := false PRODUCT_SET_DEBUGFS_RESTRICTIONS := false

View file

@ -64,6 +64,10 @@ ScopedAStatus HdmiCec::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_r
*_aidl_return = Result::FAILURE_INVALID_ARGS; *_aidl_return = Result::FAILURE_INVALID_ARGS;
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
if (mHdmiCecPorts.empty()) {
*_aidl_return = Result::FAILURE_INVALID_STATE;
return ScopedAStatus::ok();
}
cec_log_addrs cecLogAddrs; cec_log_addrs cecLogAddrs;
int ret = ioctl(mHdmiCecPorts[0]->mCecFd, CEC_ADAP_G_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; *_aidl_return = Result::FAILURE_BUSY;
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
cecLogAddrs.cec_version = CEC_OP_CEC_VERSION_1_4; cecLogAddrs.cec_version = CEC_OP_CEC_VERSION_1_4;
cecLogAddrs.vendor_id = 0x000c03; // HDMI LLC vendor ID cecLogAddrs.vendor_id = 0x000c03; // HDMI LLC vendor ID
@ -148,15 +151,18 @@ ScopedAStatus HdmiCec::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_r
} }
ScopedAStatus HdmiCec::clearLogicalAddress() { ScopedAStatus HdmiCec::clearLogicalAddress() {
if (mHdmiCecPorts.empty()) {
LOG(ERROR) << "Clear logical address failed: no CEC adapter";
return ScopedAStatus::ok();
}
cec_log_addrs cecLogAddrs; cec_log_addrs cecLogAddrs;
memset(&cecLogAddrs, 0, sizeof(cecLogAddrs)); memset(&cecLogAddrs, 0, sizeof(cecLogAddrs));
int ret = ioctl(mHdmiCecPorts[0]->mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs); int ret = ioctl(mHdmiCecPorts[0]->mCecFd, CEC_ADAP_S_LOG_ADDRS, &cecLogAddrs);
if (ret) { if (ret) {
LOG(ERROR) << "Clear logical Address failed for port " << mHdmiCecPorts[0]->mPortId LOG(ERROR) << "Clear logical Address failed for port " << mHdmiCecPorts[0]->mPortId
<< ", Error = " << strerror(errno); << ", Error = " << strerror(errno);
} }
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
@ -170,8 +176,12 @@ ScopedAStatus HdmiCec::getCecVersion(int32_t* _aidl_return) {
} }
ScopedAStatus HdmiCec::getPhysicalAddress(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); int ret = ioctl(mHdmiCecPorts[0]->mCecFd, CEC_ADAP_G_PHYS_ADDR, &addr);
if (ret) { if (ret) {
LOG(ERROR) << "Get physical address failed, Error = " << strerror(errno); LOG(ERROR) << "Get physical address failed, Error = " << strerror(errno);
@ -193,6 +203,10 @@ ScopedAStatus HdmiCec::sendMessage(const CecMessage& message, SendMessageResult*
*_aidl_return = SendMessageResult::FAIL; *_aidl_return = SendMessageResult::FAIL;
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
if (mHdmiCecPorts.empty() || message.body.size() + 1 > CEC_MAX_MSG_SIZE) {
*_aidl_return = SendMessageResult::FAIL;
return ScopedAStatus::ok();
}
cec_msg cecMsg; cec_msg cecMsg;
memset(&cecMsg, 0, sizeof(cec_msg)); memset(&cecMsg, 0, sizeof(cec_msg));
@ -297,83 +311,84 @@ void HdmiCec::release() {
} }
void HdmiCec::event_thread(HdmiCecPort* hdmiCecPort) { void HdmiCec::event_thread(HdmiCecPort* hdmiCecPort) {
struct pollfd ufds[3] = { struct pollfd ufds[2] = {
{hdmiCecPort->mCecFd, POLLIN, 0}, {hdmiCecPort->mCecFd, POLLIN | POLLPRI, 0},
{hdmiCecPort->mCecFd, POLLERR, 0},
{hdmiCecPort->mExitFd, POLLIN, 0}, {hdmiCecPort->mExitFd, POLLIN, 0},
}; };
while (1) { while (1) {
ufds[0].revents = 0; ufds[0].revents = 0;
ufds[1].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) { if (ret <= 0) {
continue; continue;
} }
if (ufds[2].revents == POLLIN) { /* Exit */ if (ufds[1].revents & POLLIN) {
break; break;
} }
if (ufds[1].revents == POLLERR) { /* CEC Event */ if (ufds[0].revents & POLLPRI) {
cec_event ev; cec_event ev;
ret = ioctl(hdmiCecPort->mCecFd, CEC_DQEVENT, &ev); ret = ioctl(hdmiCecPort->mCecFd, CEC_DQEVENT, &ev);
if (ret) { if (ret) {
LOG(ERROR) << "CEC_DQEVENT failed, Error = " << strerror(errno); LOG(ERROR) << "CEC_DQEVENT failed, Error = " << strerror(errno);
continue; continue;
} }
if (!mCecEnabled) { if (!mCecEnabled) {
continue; continue;
} }
} }
if (ufds[0].revents == POLLIN) { /* CEC Driver */ if (!(ufds[0].revents & POLLIN)) {
cec_msg msg = {}; continue;
ret = ioctl(hdmiCecPort->mCecFd, CEC_RECEIVE, &msg); }
if (ret) { cec_msg msg = {};
LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno); ret = ioctl(hdmiCecPort->mCecFd, CEC_RECEIVE, &msg);
continue; if (ret) {
} LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno);
continue;
}
if (msg.rx_status != CEC_RX_STATUS_OK) { if (msg.len == 0 || msg.len > CEC_MAX_MSG_SIZE) {
LOG(ERROR) << "msg rx_status = " << msg.rx_status; LOG(ERROR) << "Invalid CEC message length: " << msg.len;
continue; continue;
} }
if (msg.rx_status != CEC_RX_STATUS_OK) {
LOG(ERROR) << "msg rx_status = " << msg.rx_status;
continue;
}
if (!mCecEnabled) { if (!mCecEnabled) {
continue; continue;
} }
if (!mWakeupEnabled && isWakeupMessage(msg)) { if (!mWakeupEnabled && isWakeupMessage(msg)) {
LOG(DEBUG) << "Filter wakeup message"; LOG(DEBUG) << "Filter wakeup message";
continue; continue;
} }
if (!mCecControlEnabled && !isTransferableInSleep(msg)) { if (!mCecControlEnabled && !isTransferableInSleep(msg)) {
LOG(DEBUG) << "Filter message in standby mode"; LOG(DEBUG) << "Filter message in standby mode";
continue; continue;
} }
if (mCallback != nullptr) { 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),
CecMessage cecMessage{ static_cast<size_t>(CEC_MESSAGE_BODY_MAX_LENGTH - 1));
.initiator = static_cast<CecLogicalAddress>(msg.msg[0] >> 4), CecMessage cecMessage{
.destination = static_cast<CecLogicalAddress>(msg.msg[0] & 0xf), .initiator = static_cast<CecLogicalAddress>(msg.msg[0] >> 4),
}; .destination = static_cast<CecLogicalAddress>(msg.msg[0] & 0xf),
cecMessage.body.resize(length); };
for (size_t i = 0; i < length; ++i) { cecMessage.body.resize(length);
cecMessage.body[i] = static_cast<uint8_t>(msg.msg[i + 1]); for (size_t i = 0; i < length; ++i) {
} cecMessage.body[i] = static_cast<uint8_t>(msg.msg[i + 1]);
mCallback->onCecMessage(cecMessage);
} else {
LOG(ERROR) << "no event callback for message";
} }
mCallback->onCecMessage(cecMessage);
} else {
LOG(ERROR) << "no event callback for message";
} }
} }
} }

View file

@ -19,12 +19,16 @@
#include "HdmiConnection.h" #include "HdmiConnection.h"
#include <android-base/file.h>
#include <android-base/logging.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 ndk::ScopedAStatus;
using std::string;
namespace android { namespace android {
namespace hardware { namespace hardware {
@ -33,26 +37,49 @@ namespace hdmi {
namespace connection { namespace connection {
namespace implementation { 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() { HdmiConnection::HdmiConnection() {
mCallback = nullptr; mPortInfos.push_back({.type = HdmiPortType::OUTPUT,
.portId = 1,
.cecSupported = true,
.arcSupported = false,
.eArcSupported = false,
.physicalAddress = 0xFFFF});
mHpdSignal.push_back(HpdSignal::HDMI_HPD_PHYSICAL);
for (int i = 0; i < 2; i++) { mStopFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
mPortInfos.push_back( if (mStopFd < 0) {
{.type = HdmiPortType::OUTPUT, PLOG(ERROR) << "Failed to create HDMI monitor eventfd";
.portId = i, return;
.cecSupported = true,
.arcSupported = false,
.eArcSupported = false,
.physicalAddress = 0xFFFF});
mHpdSignal.push_back(HpdSignal::HDMI_HPD_PHYSICAL);
} }
mMonitorThread = std::thread(&HdmiConnection::monitorLoop, this);
} }
HdmiConnection::~HdmiConnection() { HdmiConnection::~HdmiConnection() {
if (mCallback != nullptr) { signalMonitorStop();
mCallback = nullptr; 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) { 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; *_aidl_return = false;
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
bool connected = false; bool connected = false;
string hdmiStatusPath = "/sys/class/drm/" + drmCard + "-HDMI-A-" + to_string(portId + 1) + "/status"; if (!readStatus(fd, &connected)) {
if (!access(hdmiStatusPath.c_str(), R_OK)) { connected = false;
string connectedValue;
if (ReadFileToString(hdmiStatusPath, &connectedValue)) {
connected = !connectedValue.compare("connected\n");
}
} }
LOG(INFO) << "portId: " << portId << ", connected: " << connected; close(fd);
LOG(INFO) << "portId: " << portId << ", connected: " << connected;
*_aidl_return = connected; *_aidl_return = connected;
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
ScopedAStatus HdmiConnection::setCallback(const std::shared_ptr<IHdmiConnectionCallback>& callback) { ScopedAStatus HdmiConnection::setCallback(
if (mCallback != nullptr) { const std::shared_ptr<IHdmiConnectionCallback>& callback) {
mCallback = nullptr; std::lock_guard<std::mutex> lock(mCallbackMutex);
} mCallback = callback;
if (callback != nullptr) {
mCallback = callback;
}
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
ScopedAStatus HdmiConnection::setHpdSignal(HpdSignal signal, int32_t portId) { ScopedAStatus HdmiConnection::setHpdSignal(HpdSignal signal, int32_t portId) {
if (portId != 0 && portId != 1) { if (portId != 1) {
return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
} }
mHpdSignal.at(portId) = signal; mHpdSignal[0] = signal;
return ScopedAStatus::ok(); return ScopedAStatus::ok();
} }
ScopedAStatus HdmiConnection::getHpdSignal(int32_t portId, HpdSignal* _aidl_return) { ScopedAStatus HdmiConnection::getHpdSignal(int32_t portId, HpdSignal* _aidl_return) {
if (portId != 0 && portId != 1) { if (portId != 1) {
return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
} }
*_aidl_return = mHpdSignal.at(portId); *_aidl_return = mHpdSignal[0];
return ScopedAStatus::ok(); 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 implementation
} // namespace connection } // namespace connection
} // namespace hdmi } // namespace hdmi

View file

@ -18,8 +18,10 @@
#include <aidl/android/hardware/tv/hdmi/connection/BnHdmiConnection.h> #include <aidl/android/hardware/tv/hdmi/connection/BnHdmiConnection.h>
#include <aidl/android/hardware/tv/hdmi/connection/Result.h> #include <aidl/android/hardware/tv/hdmi/connection/Result.h>
#include <algorithm> #include <algorithm>
#include <memory>
#include <mutex>
#include <thread>
#include <vector> #include <vector>
using namespace std; using namespace std;
namespace android { namespace android {
@ -48,11 +50,16 @@ struct HdmiConnection : public BnHdmiConnection {
::ndk::ScopedAStatus getHpdSignal(int32_t portId, HpdSignal* _aidl_return) override; ::ndk::ScopedAStatus getHpdSignal(int32_t portId, HpdSignal* _aidl_return) override;
private: private:
void monitorLoop();
void signalMonitorStop();
std::vector<HdmiPortInfo> mPortInfos; std::vector<HdmiPortInfo> mPortInfos;
std::vector<HpdSignal> mHpdSignal; std::vector<HpdSignal> mHpdSignal;
std::mutex mCallbackMutex;
std::shared_ptr<IHdmiConnectionCallback> mCallback; std::shared_ptr<IHdmiConnectionCallback> mCallback;
}; std::thread mMonitorThread;
int mStopFd = -1;
} // namespace implementation } // namespace implementation
} // namespace connection } // namespace connection

View file

@ -6,6 +6,9 @@
/dev/v4l-subdev* 0660 system camera /dev/v4l-subdev* 0660 system camera
/dev/video* 0660 system camera /dev/video* 0660 system camera
# HDMI CEC
/dev/cec0 0660 system system
# DMA # DMA
/dev/dma_heap/linux,cma 0666 system graphics /dev/dma_heap/linux,cma 0666 system graphics
/dev/dma_heap/system 0666 system graphics /dev/dma_heap/system 0666 system graphics

View file

@ -50,9 +50,9 @@ persist.bluetooth.a2dp_aac.vbr_supported=true
ro.hardware.camera=libcamera ro.hardware.camera=libcamera
# CEC # CEC
#persist.vendor.hdmi.cec_device=cec0 persist.vendor.hdmi.cec_device=cec0
#ro.hdmi.cec_device_types=playback_device ro.hdmi.cec_device_types=playback_device
#ro.hdmi.device_type=4 ro.hdmi.device_type=4
# Chipset # Chipset
ro.soc.manufacturer=Rockchip ro.soc.manufacturer=Rockchip