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.
243 lines
6.6 KiB
C++
243 lines
6.6 KiB
C++
/*
|
|
* 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 <android-base/logging.h>
|
|
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <sys/eventfd.h>
|
|
#include <unistd.h>
|
|
|
|
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<HdmiPortInfo>* _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<IHdmiConnectionCallback>& callback) {
|
|
std::lock_guard<std::mutex> 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<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
|
|
} // namespace tv
|
|
} // namespace hardware
|
|
} // namespace android
|