/* * Copyright (C) 2022 The Android Open Source Project * Copyright (C) 2025 KonstaKANG * * 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.hdmi.connection-service.opi" #include "HdmiConnection.h" #include #include #include #include #include #include #include using ndk::ScopedAStatus; namespace android { namespace hardware { namespace tv { namespace hdmi { namespace connection { namespace implementation { 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() { 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() { signalMonitorStop(); if (mMonitorThread.joinable()) { mMonitorThread.join(); } if (mStopFd >= 0) { close(mStopFd); mStopFd = -1; } } ScopedAStatus HdmiConnection::getPortInfo(std::vector* _aidl_return) { *_aidl_return = mPortInfos; return ScopedAStatus::ok(); } ScopedAStatus HdmiConnection::isConnected(int32_t portId, bool* _aidl_return) { 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; if (!readStatus(fd, &connected)) { connected = false; } close(fd); LOG(INFO) << "portId: " << portId << ", connected: " << connected; *_aidl_return = connected; return ScopedAStatus::ok(); } 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 != 1) { return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } mHpdSignal[0] = signal; return ScopedAStatus::ok(); } ScopedAStatus HdmiConnection::getHpdSignal(int32_t portId, HpdSignal* _aidl_return) { if (portId != 1) { return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } *_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 } // namespace tv } // namespace hardware } // namespace android