diff --git a/device.mk b/device.mk index cd85af9..05fe833 100644 --- a/device.mk +++ b/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 diff --git a/hdmi/cec/HdmiCec.cpp b/hdmi/cec/HdmiCec.cpp index 8ca412b..39b9200 100644 --- a/hdmi/cec/HdmiCec.cpp +++ b/hdmi/cec/HdmiCec.cpp @@ -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(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,83 +311,84 @@ 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 */ - cec_msg msg = {}; - ret = ioctl(hdmiCecPort->mCecFd, CEC_RECEIVE, &msg); + if (!(ufds[0].revents & POLLIN)) { + continue; + } - if (ret) { - LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno); - continue; - } + cec_msg msg = {}; + ret = ioctl(hdmiCecPort->mCecFd, CEC_RECEIVE, &msg); + if (ret) { + LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno); + continue; + } - if (msg.rx_status != CEC_RX_STATUS_OK) { - LOG(ERROR) << "msg rx_status = " << msg.rx_status; - 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; + } - if (!mCecEnabled) { - continue; - } + if (!mCecEnabled) { + continue; + } - if (!mWakeupEnabled && isWakeupMessage(msg)) { - LOG(DEBUG) << "Filter wakeup message"; - continue; - } + if (!mWakeupEnabled && isWakeupMessage(msg)) { + LOG(DEBUG) << "Filter wakeup message"; + continue; + } - if (!mCecControlEnabled && !isTransferableInSleep(msg)) { - LOG(DEBUG) << "Filter message in standby mode"; - continue; - } + if (!mCecControlEnabled && !isTransferableInSleep(msg)) { + LOG(DEBUG) << "Filter message in standby mode"; + continue; + } - if (mCallback != nullptr) { - size_t length = std::min(msg.len - 1, (uint32_t)(CEC_MESSAGE_BODY_MAX_LENGTH - 1)); - CecMessage cecMessage{ - .initiator = static_cast(msg.msg[0] >> 4), - .destination = static_cast(msg.msg[0] & 0xf), - }; - cecMessage.body.resize(length); - for (size_t i = 0; i < length; ++i) { - cecMessage.body[i] = static_cast(msg.msg[i + 1]); - } - mCallback->onCecMessage(cecMessage); - } else { - LOG(ERROR) << "no event callback for message"; + if (mCallback != nullptr) { + size_t length = std::min(static_cast(msg.len - 1), + static_cast(CEC_MESSAGE_BODY_MAX_LENGTH - 1)); + CecMessage cecMessage{ + .initiator = static_cast(msg.msg[0] >> 4), + .destination = static_cast(msg.msg[0] & 0xf), + }; + cecMessage.body.resize(length); + for (size_t i = 0; i < length; ++i) { + cecMessage.body[i] = static_cast(msg.msg[i + 1]); } + mCallback->onCecMessage(cecMessage); + } else { + LOG(ERROR) << "no event callback for message"; } } } diff --git a/hdmi/connection/HdmiConnection.cpp b/hdmi/connection/HdmiConnection.cpp index 9bd6c57..908e3c2 100644 --- a/hdmi/connection/HdmiConnection.cpp +++ b/hdmi/connection/HdmiConnection.cpp @@ -19,12 +19,16 @@ #include "HdmiConnection.h" -#include #include -using android::base::ReadFileToString; +#include +#include +#include +#include +#include +#include + 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; + 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++) { - mPortInfos.push_back( - {.type = HdmiPortType::OUTPUT, - .portId = i, - .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* _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& callback) { - if (mCallback != nullptr) { - mCallback = nullptr; - } - - if (callback != nullptr) { - mCallback = callback; - } - +ScopedAStatus HdmiConnection::setCallback( + const std::shared_ptr& callback) { + std::lock_guard 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 callback; + { + std::lock_guard 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 diff --git a/hdmi/connection/HdmiConnection.h b/hdmi/connection/HdmiConnection.h index 716b9da..fdac842 100644 --- a/hdmi/connection/HdmiConnection.h +++ b/hdmi/connection/HdmiConnection.h @@ -18,8 +18,10 @@ #include #include #include +#include +#include +#include #include - 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 mPortInfos; std::vector mHpdSignal; + std::mutex mCallbackMutex; std::shared_ptr mCallback; -}; + std::thread mMonitorThread; + int mStopFd = -1; } // namespace implementation } // namespace connection diff --git a/ramdisk/ueventd.tart.rc b/ramdisk/ueventd.tart.rc index 6345e5b..fe1f1f2 100644 --- a/ramdisk/ueventd.tart.rc +++ b/ramdisk/ueventd.tart.rc @@ -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 diff --git a/vendor.prop b/vendor.prop index aab27a2..00a1ada 100644 --- a/vendor.prop +++ b/vendor.prop @@ -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