blob: 4dbd339706705e8ece62a2f17bacbee49fe870fa [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* 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.cec"
#include <android-base/logging.h>
#include <fcntl.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/hdmi_cec.h>
#include "HdmiCecMock.h"
using ndk::ScopedAStatus;
namespace android {
namespace hardware {
namespace tv {
namespace hdmi {
namespace cec {
namespace implementation {
void HdmiCecMock::serviceDied(void* cookie) {
ALOGE("HdmiCecMock died");
auto hdmiCecMock = static_cast<HdmiCecMock*>(cookie);
hdmiCecMock->closeCallback();
}
ScopedAStatus HdmiCecMock::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_return) {
// Have a list to maintain logical addresses
mLogicalAddresses.push_back(addr);
*_aidl_return = Result::SUCCESS;
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::clearLogicalAddress() {
// Remove logical address from the list
mLogicalAddresses = {};
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::enableAudioReturnChannel(int32_t portId __unused, bool enable __unused) {
// Maintain ARC status
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::getCecVersion(int32_t* _aidl_return) {
// Maintain a cec version and return it
*_aidl_return = mCecVersion;
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::getPhysicalAddress(int32_t* _aidl_return) {
// Maintain a physical address and return it
// Default 0xFFFF, update on hotplug event
*_aidl_return = mPhysicalAddress;
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::getVendorId(int32_t* _aidl_return) {
*_aidl_return = mCecVendorId;
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::sendMessage(const CecMessage& message, SendMessageResult* _aidl_return) {
if (message.body.size() == 0) {
*_aidl_return = SendMessageResult::NACK;
} else {
sendMessageToFifo(message);
*_aidl_return = SendMessageResult::SUCCESS;
}
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::setCallback(const std::shared_ptr<IHdmiCecCallback>& callback) {
closeCallback();
if (callback != nullptr) {
mCallback = callback;
mDeathRecipient =
ndk::ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(serviceDied));
AIBinder_linkToDeath(callback->asBinder().get(), mDeathRecipient.get(), this /* cookie */);
mInputFile = open(CEC_MSG_IN_FIFO, O_RDWR | O_CLOEXEC);
mOutputFile = open(CEC_MSG_OUT_FIFO, O_RDWR | O_CLOEXEC);
pthread_create(&mThreadId, NULL, __threadLoop, this);
pthread_setname_np(mThreadId, "hdmi_cec_loop");
}
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::setLanguage(const std::string& language) {
if (language.size() != 3) {
ALOGE("[halimp_aidl] Wrong language code: expected 3 letters, but it was %zu",
language.size());
return ScopedAStatus::ok();
}
// TODO Validate if language is a valid language code
const char* languageStr = language.c_str();
int convertedLanguage = ((languageStr[0] & 0xFF) << 16) | ((languageStr[1] & 0xFF) << 8) |
(languageStr[2] & 0xFF);
mOptionLanguage = convertedLanguage;
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::enableWakeupByOtp(bool value) {
mOptionWakeUp = value;
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::enableCec(bool value) {
mOptionEnableCec = value;
return ScopedAStatus::ok();
}
ScopedAStatus HdmiCecMock::enableSystemCecControl(bool value) {
mOptionSystemCecControl = value;
return ScopedAStatus::ok();
}
void* HdmiCecMock::__threadLoop(void* user) {
HdmiCecMock* const self = static_cast<HdmiCecMock*>(user);
self->threadLoop();
return 0;
}
int HdmiCecMock::readMessageFromFifo(unsigned char* buf, int msgCount) {
if (msgCount <= 0 || !buf) {
return 0;
}
int ret = -1;
// Maybe blocked at driver
ret = read(mInputFile, buf, msgCount);
if (ret < 0) {
ALOGE("[halimp_aidl] read :%s failed, ret:%d\n", CEC_MSG_IN_FIFO, ret);
return -1;
}
return ret;
}
int HdmiCecMock::sendMessageToFifo(const CecMessage& message) {
unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH + 1] = {0};
int ret = -1;
msgBuf[0] = ((static_cast<uint8_t>(message.initiator) & 0xf) << 4) |
(static_cast<uint8_t>(message.destination) & 0xf);
size_t length = std::min(static_cast<size_t>(message.body.size()),
static_cast<size_t>(CEC_MESSAGE_BODY_MAX_LENGTH));
for (size_t i = 0; i < length; ++i) {
msgBuf[i + 1] = static_cast<unsigned char>(message.body[i]);
}
// Open the output pipe for writing outgoing cec message
mOutputFile = open(CEC_MSG_OUT_FIFO, O_WRONLY | O_CLOEXEC);
if (mOutputFile < 0) {
ALOGE("[halimp_aidl] file open failed for writing");
return -1;
}
// Write message into the output pipe
ret = write(mOutputFile, msgBuf, length + 1);
close(mOutputFile);
if (ret < 0) {
ALOGE("[halimp_aidl] write :%s failed, ret:%d\n", CEC_MSG_OUT_FIFO, ret);
return -1;
}
return ret;
}
void HdmiCecMock::printCecMsgBuf(const char* msg_buf, int len) {
int i, size = 0;
const int bufSize = CEC_MESSAGE_BODY_MAX_LENGTH * 3;
// Use 2 characters for each byte in the message plus 1 space
char buf[bufSize] = {0};
// Messages longer than max length will be truncated.
for (i = 0; i < len && size < bufSize; i++) {
size += sprintf(buf + size, " %02x", msg_buf[i]);
}
ALOGD("[halimp_aidl] %s, msg:%.*s", __FUNCTION__, size, buf);
}
void HdmiCecMock::handleCecMessage(unsigned char* msgBuf, int msgSize) {
CecMessage message;
size_t length = std::min(static_cast<size_t>(msgSize - 1),
static_cast<size_t>(CEC_MESSAGE_BODY_MAX_LENGTH));
message.body.resize(length);
for (size_t i = 0; i < length; ++i) {
message.body[i] = static_cast<uint8_t>(msgBuf[i + 1]);
ALOGD("[halimp_aidl] msg body %x", message.body[i]);
}
message.initiator = static_cast<CecLogicalAddress>((msgBuf[0] >> 4) & 0xf);
ALOGD("[halimp_aidl] msg init %hhd", message.initiator);
message.destination = static_cast<CecLogicalAddress>((msgBuf[0] >> 0) & 0xf);
ALOGD("[halimp_aidl] msg dest %hhd", message.destination);
if (mCallback != nullptr) {
mCallback->onCecMessage(message);
}
}
void HdmiCecMock::threadLoop() {
ALOGD("[halimp_aidl] threadLoop start.");
unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH];
int r = -1;
// Open the input pipe
while (mCecThreadRun && mInputFile < 0) {
usleep(1000 * 1000);
mInputFile = open(CEC_MSG_IN_FIFO, O_RDONLY | O_CLOEXEC);
}
ALOGD("[halimp_aidl] file open ok, fd = %d.", mInputFile);
while (mCecThreadRun) {
if (!mOptionSystemCecControl) {
usleep(1000 * 1000);
continue;
}
memset(msgBuf, 0, sizeof(msgBuf));
// Try to get a message from dev.
// echo -n -e '\x04\x83' >> /dev/cec
r = readMessageFromFifo(msgBuf, CEC_MESSAGE_BODY_MAX_LENGTH);
if (r <= 1) {
// Ignore received ping messages
continue;
}
printCecMsgBuf((const char*)msgBuf, r);
if (((msgBuf[0] >> 4) & 0xf) == 0xf) {
// The message is a hotplug event, handled by HDMI HAL.
continue;
}
handleCecMessage(msgBuf, r);
}
ALOGD("[halimp_aidl] thread end.");
}
HdmiCecMock::HdmiCecMock() {
ALOGD("[halimp_aidl] Opening a virtual CEC HAL for testing and virtual machine.");
mCallback = nullptr;
mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(nullptr);
}
void HdmiCecMock::closeCallback() {
if (mCallback != nullptr) {
ALOGD("[halimp_aidl] HdmiCecMock close the current callback.");
mCallback = nullptr;
mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(nullptr);
mCecThreadRun = false;
pthread_join(mThreadId, NULL);
}
}
HdmiCecMock::~HdmiCecMock() {
ALOGD("[halimp_aidl] HdmiCecMock shutting down.");
closeCallback();
}
} // namespace implementation
} // namespace cec
} // namespace hdmi
} // namespace tv
} // namespace hardware
} // namespace android