diff --git a/tv/cec/1.1/default/Android.bp b/tv/cec/1.1/default/Android.bp new file mode 100644 index 0000000000..e0dff0d033 --- /dev/null +++ b/tv/cec/1.1/default/Android.bp @@ -0,0 +1,23 @@ +cc_binary { + name: "android.hardware.tv.cec@1.1-service", + defaults: ["hidl_defaults"], + vintf_fragments: ["android.hardware.tv.cec@1.1-service.xml"], + relative_install_path: "hw", + vendor: true, + init_rc: ["android.hardware.tv.cec@1.1-service.rc"], + srcs: [ + "serviceMock.cpp", + "HdmiCecMock.cpp", + ], + + shared_libs: [ + "liblog", + "libcutils", + "libbase", + "libutils", + "libhardware", + "libhidlbase", + "android.hardware.tv.cec@1.0", + "android.hardware.tv.cec@1.1", + ], +} diff --git a/tv/cec/1.1/default/HdmiCecMock.cpp b/tv/cec/1.1/default/HdmiCecMock.cpp new file mode 100644 index 0000000000..f65bab9865 --- /dev/null +++ b/tv/cec/1.1/default/HdmiCecMock.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2021 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.cec@1.1" +#include +#include + +#include +#include +#include "HdmiCecMock.h" + +namespace android { +namespace hardware { +namespace tv { +namespace cec { +namespace V1_1 { +namespace implementation { + +class WrappedCallback : public ::android::hardware::tv::cec::V1_1::IHdmiCecCallback { + public: + WrappedCallback(sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback> callback) { + mCallback = callback; + } + + Return onCecMessage(const ::android::hardware::tv::cec::V1_0::CecMessage& message) { + mCallback->onCecMessage(message); + return Void(); + } + Return onCecMessage_1_1(const ::android::hardware::tv::cec::V1_1::CecMessage& message) { + ::android::hardware::tv::cec::V1_0::CecMessage cecMessage; + cecMessage.initiator = + ::android::hardware::tv::cec::V1_0::CecLogicalAddress(message.initiator); + cecMessage.destination = + ::android::hardware::tv::cec::V1_0::CecLogicalAddress(message.destination); + cecMessage.body = message.body; + mCallback->onCecMessage(cecMessage); + return Void(); + } + Return onHotplugEvent(const ::android::hardware::tv::cec::V1_0::HotplugEvent& event) { + mCallback->onHotplugEvent(event); + return Void(); + } + + private: + sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback> mCallback; +}; + +/* + * (*set_option)() passes flags controlling the way HDMI-CEC service works down + * to HAL implementation. Those flags will be used in case the feature needs + * update in HAL itself, firmware or microcontroller. + */ +void HdmiCecMock::cec_set_option(int flag, int value) { + // maintain options and set them accordingly + switch (flag) { + case HDMI_OPTION_WAKEUP: + mOptionWakeUp = value; + break; + case HDMI_OPTION_ENABLE_CEC: + mOptionEnableCec = value; + break; + case HDMI_OPTION_SYSTEM_CEC_CONTROL: + mOptionSystemCecControl = value; + break; + case HDMI_OPTION_SET_LANG: + mOptionLanguage = value; + break; + } +} + +// Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow. +Return HdmiCecMock::addLogicalAddress(CecLogicalAddress addr) { + return addLogicalAddress_1_1(::android::hardware::tv::cec::V1_1::CecLogicalAddress(addr)); +} + +Return HdmiCecMock::clearLogicalAddress() { + // remove logical address from the list + mLogicalAddresses = {}; + return Void(); +} + +Return HdmiCecMock::getPhysicalAddress(getPhysicalAddress_cb _hidl_cb) { + // maintain a physical address and return it + // default 0xFFFF, update on hotplug event + _hidl_cb(Result::SUCCESS, mPhysicalAddress); + return Void(); +} + +Return HdmiCecMock::sendMessage(const CecMessage& message) { + ::android::hardware::tv::cec::V1_1::CecMessage cecMessage; + cecMessage.initiator = ::android::hardware::tv::cec::V1_1::CecLogicalAddress(message.initiator); + cecMessage.destination = + ::android::hardware::tv::cec::V1_1::CecLogicalAddress(message.destination); + cecMessage.body = message.body; + return sendMessage_1_1(cecMessage); +} + +Return HdmiCecMock::setCallback(const sp& callback) { + return setCallback_1_1(new WrappedCallback(callback)); +} + +Return HdmiCecMock::getCecVersion() { + // maintain a cec version and return it + return mCecVersion; +} + +Return HdmiCecMock::getVendorId() { + return mCecVendorId; +} + +Return HdmiCecMock::getPortInfo(getPortInfo_cb _hidl_cb) { + // TODO ready port info from device specific config + _hidl_cb(mPortInfo); + return Void(); +} + +Return HdmiCecMock::setOption(OptionKey key, bool value) { + cec_set_option(static_cast(key), value ? 1 : 0); + return Void(); +} + +Return HdmiCecMock::setLanguage(const hidl_string& language) { + if (language.size() != 3) { + LOG(ERROR) << "Wrong language code: expected 3 letters, but it was " << language.size() + << "."; + return Void(); + } + // 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); + cec_set_option(HDMI_OPTION_SET_LANG, convertedLanguage); + return Void(); +} + +Return HdmiCecMock::enableAudioReturnChannel(int32_t portId __unused, bool enable __unused) { + // Maintain ARC status + return Void(); +} + +Return HdmiCecMock::isConnected(int32_t portId) { + // maintain port connection status and update on hotplug event + if (portId < mTotalPorts && portId >= 0) { + return mPortConnectionStatus[portId]; + } + return false; +} + +// Methods from ::android::hardware::tv::cec::V1_1::IHdmiCec follow. +Return HdmiCecMock::addLogicalAddress_1_1( + ::android::hardware::tv::cec::V1_1::CecLogicalAddress addr) { + // have a list to maintain logical addresses + int size = mLogicalAddresses.size(); + mLogicalAddresses.resize(size + 1); + mLogicalAddresses[size + 1] = addr; + return Result::SUCCESS; +} + +Return HdmiCecMock::sendMessage_1_1( + const ::android::hardware::tv::cec::V1_1::CecMessage& message) { + if (message.body.size() == 0) { + return SendMessageResult::NACK; + } + sendMessageToFifo(message); + return SendMessageResult::SUCCESS; +} + +Return HdmiCecMock::setCallback_1_1( + const sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback>& callback) { + if (mCallback != nullptr) { + mCallback = nullptr; + } + + if (callback != nullptr) { + mCallback = callback; + mCallback->linkToDeath(this, 0 /*cookie*/); + + mInputFile = open(CEC_MSG_IN_FIFO, O_RDWR); + mOutputFile = open(CEC_MSG_OUT_FIFO, O_RDWR); + pthread_create(&mThreadId, NULL, __threadLoop, this); + pthread_setname_np(mThreadId, "hdmi_cec_loop"); + } + return Void(); +} + +void* HdmiCecMock::__threadLoop(void* user) { + HdmiCecMock* const self = static_cast(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] read :%s failed, ret:%d\n", CEC_MSG_IN_FIFO, ret); + return -1; + } + + return ret; +} + +int HdmiCecMock::sendMessageToFifo(const ::android::hardware::tv::cec::V1_1::CecMessage& message) { + unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH]; + int ret = -1; + + memset(msgBuf, 0, sizeof(msgBuf)); + msgBuf[0] = ((static_cast(message.initiator) & 0xf) << 4) | + (static_cast(message.destination) & 0xf); + + size_t length = std::min(static_cast(message.body.size()), + static_cast(MaxLength::MESSAGE_BODY)); + for (size_t i = 0; i < length; ++i) { + msgBuf[i + 1] = static_cast(message.body[i]); + } + + // open the output pipe for writing outgoing cec message + mOutputFile = open(CEC_MSG_OUT_FIFO, O_WRONLY); + if (mOutputFile < 0) { + ALOGD("[halimp] 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] write :%s failed, ret:%d\n", CEC_MSG_OUT_FIFO, ret); + return -1; + } + return ret; +} + +void HdmiCecMock::printCecMsgBuf(const char* msg_buf, int len) { + char buf[64] = {}; + int i, size = 0; + memset(buf, 0, sizeof(buf)); + for (i = 0; i < len; i++) { + size += sprintf(buf + size, " %02x", msg_buf[i]); + } + ALOGD("[halimp] %s, msg:%s", __FUNCTION__, buf); +} + +void HdmiCecMock::handleHotplugMessage(unsigned char* msgBuf) { + HotplugEvent hotplugEvent{.connected = ((msgBuf[3]) & 0xf) > 0, + .portId = static_cast(msgBuf[0] & 0xf)}; + + if (hotplugEvent.portId >= mPortInfo.size()) { + ALOGD("[halimp] ignore hot plug message, id %x does not exist", hotplugEvent.portId); + return; + } + + ALOGD("[halimp] hot plug port id %x, is connected %x", (msgBuf[0] & 0xf), (msgBuf[3] & 0xf)); + if (mPortInfo[hotplugEvent.portId].type == HdmiPortType::OUTPUT) { + mPhysicalAddress = + ((hotplugEvent.connected == 0) ? 0xffff : ((msgBuf[1] << 8) | (msgBuf[2]))); + mPortInfo[hotplugEvent.portId].physicalAddress = mPhysicalAddress; + ALOGD("[halimp] hot plug physical address %x", mPhysicalAddress); + } + + // todo update connection status + + if (mCallback != nullptr) { + mCallback->onHotplugEvent(hotplugEvent); + } +} + +void HdmiCecMock::handleCecMessage(unsigned char* msgBuf, int megSize) { + ::android::hardware::tv::cec::V1_1::CecMessage message; + size_t length = std::min(static_cast(megSize - 1), + static_cast(MaxLength::MESSAGE_BODY)); + message.body.resize(length); + + for (size_t i = 0; i < length; ++i) { + message.body[i] = static_cast(msgBuf[i + 1]); + ALOGD("[halimp] msg body %x", message.body[i]); + } + + message.initiator = static_cast<::android::hardware::tv::cec::V1_1::CecLogicalAddress>( + (msgBuf[0] >> 4) & 0xf); + ALOGD("[halimp] msg init %x", message.initiator); + message.destination = static_cast<::android::hardware::tv::cec::V1_1::CecLogicalAddress>( + (msgBuf[0] >> 0) & 0xf); + ALOGD("[halimp] msg dest %x", message.destination); + + // messageValidateAndHandle(&event); + + if (mCallback != nullptr) { + mCallback->onCecMessage_1_1(message); + } +} + +void HdmiCecMock::threadLoop() { + ALOGD("[halimp] threadLoop start."); + unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH]; + int r = -1; + + // open the input pipe + while (mInputFile < 0) { + usleep(1000 * 1000); + mInputFile = open(CEC_MSG_IN_FIFO, O_RDONLY); + } + ALOGD("[halimp] 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 + handleHotplugMessage(msgBuf); + continue; + } + + handleCecMessage(msgBuf, r); + } + + ALOGD("[halimp] thread end."); + // mCecDevice.mExited = true; +} + +HdmiCecMock::HdmiCecMock() { + ALOGE("[halimp] Opening a virtual HAL for testing and virtual machine."); + mCallback = nullptr; + mPortInfo.resize(mTotalPorts); + mPortConnectionStatus.resize(mTotalPorts); + mPortInfo[0] = {.type = HdmiPortType::OUTPUT, + .portId = static_cast(1), + .cecSupported = true, + .arcSupported = false, + .physicalAddress = mPhysicalAddress}; + mPortConnectionStatus[0] = false; +} + +} // namespace implementation +} // namespace V1_1 +} // namespace cec +} // namespace tv +} // namespace hardware +} // namespace android \ No newline at end of file diff --git a/tv/cec/1.1/default/HdmiCecMock.h b/tv/cec/1.1/default/HdmiCecMock.h new file mode 100644 index 0000000000..0205f8d95f --- /dev/null +++ b/tv/cec/1.1/default/HdmiCecMock.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 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. + */ + +#include +#include +#include +#include + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace cec { +namespace V1_1 { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::tv::cec::V1_0::CecLogicalAddress; +using ::android::hardware::tv::cec::V1_0::CecMessage; +using ::android::hardware::tv::cec::V1_0::HdmiPortInfo; +using ::android::hardware::tv::cec::V1_0::HdmiPortType; +using ::android::hardware::tv::cec::V1_0::HotplugEvent; +using ::android::hardware::tv::cec::V1_0::IHdmiCecCallback; +using ::android::hardware::tv::cec::V1_0::MaxLength; +using ::android::hardware::tv::cec::V1_0::OptionKey; +using ::android::hardware::tv::cec::V1_0::Result; +using ::android::hardware::tv::cec::V1_0::SendMessageResult; +using ::android::hardware::tv::cec::V1_1::IHdmiCec; + +#define CEC_MSG_IN_FIFO "/dev/cec_in_pipe" +#define CEC_MSG_OUT_FIFO "/dev/cec_out_pipe" + +struct HdmiCecMock : public IHdmiCec, public hidl_death_recipient { + HdmiCecMock(); + // Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow. + Return addLogicalAddress(CecLogicalAddress addr) override; + Return clearLogicalAddress() override; + Return getPhysicalAddress(getPhysicalAddress_cb _hidl_cb) override; + Return sendMessage(const CecMessage& message) override; + Return setCallback( + const sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback>& callback) override; + Return getCecVersion() override; + Return getVendorId() override; + Return getPortInfo(getPortInfo_cb _hidl_cb) override; + Return setOption(OptionKey key, bool value) override; + Return setLanguage(const hidl_string& language) override; + Return enableAudioReturnChannel(int32_t portId, bool enable) override; + Return isConnected(int32_t portId) override; + + // Methods from ::android::hardware::tv::cec::V1_1::IHdmiCec follow. + Return addLogicalAddress_1_1( + ::android::hardware::tv::cec::V1_1::CecLogicalAddress addr) override; + Return sendMessage_1_1( + const ::android::hardware::tv::cec::V1_1::CecMessage& message) override; + Return setCallback_1_1( + const sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback>& callback) override; + + virtual void serviceDied(uint64_t /*cookie*/, + const wp<::android::hidl::base::V1_0::IBase>& /*who*/) { + setCallback(nullptr); + } + + void cec_set_option(int flag, int value); + void printCecMsgBuf(const char* msg_buf, int len); + + private: + static void* __threadLoop(void* data); + void threadLoop(); + int readMessageFromFifo(unsigned char* buf, int msgCount); + int sendMessageToFifo(const ::android::hardware::tv::cec::V1_1::CecMessage& message); + void handleHotplugMessage(unsigned char* msgBuf); + void handleCecMessage(unsigned char* msgBuf, int length); + + private: + sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback> mCallback; + + // Variables for the virtual cec hal impl + uint16_t mPhysicalAddress = 0xFFFF; + vector<::android::hardware::tv::cec::V1_1::CecLogicalAddress> mLogicalAddresses; + int32_t mCecVersion = 0x06; + uint32_t mCecVendorId = 0x01; + + // Port configuration + int mTotalPorts = 1; + hidl_vec mPortInfo; + hidl_vec mPortConnectionStatus; + + // CEC Option value + int mOptionWakeUp = 0; + int mOptionEnableCec = 0; + int mOptionSystemCecControl = 0; + int mOptionLanguage = 0; + + // Testing variables + // Input file descriptor + int mInputFile; + // Output file descriptor + int mOutputFile; + bool mCecThreadRun = true; + pthread_t mThreadId = 0; +}; +} // namespace implementation +} // namespace V1_1 +} // namespace cec +} // namespace tv +} // namespace hardware +} // namespace android \ No newline at end of file diff --git a/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.rc b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.rc new file mode 100644 index 0000000000..e150c91cc7 --- /dev/null +++ b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.rc @@ -0,0 +1,6 @@ +service vendor.cec-hal-1-1 /vendor/bin/hw/android.hardware.tv.cec@1.1-service + interface android.hardware.tv.cec@1.0::IHdmiCec default + interface android.hardware.tv.cec@1.1::IHdmiCec default + class hal + user system + group system \ No newline at end of file diff --git a/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.xml b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.xml new file mode 100644 index 0000000000..492369e67f --- /dev/null +++ b/tv/cec/1.1/default/android.hardware.tv.cec@1.1-service.xml @@ -0,0 +1,11 @@ + + + android.hardware.tv.cec + hwbinder + 1.1 + + IHdmiCec + default + + + diff --git a/tv/cec/1.1/default/serviceMock.cpp b/tv/cec/1.1/default/serviceMock.cpp new file mode 100644 index 0000000000..72fc311ddc --- /dev/null +++ b/tv/cec/1.1/default/serviceMock.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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.cec@1.1-service-shim" + +#include +#include +#include "HdmiCecMock.h" + +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; +using android::hardware::tv::cec::V1_1::IHdmiCec; +using android::hardware::tv::cec::V1_1::implementation::HdmiCecMock; + +int main() { + configureRpcThreadpool(8, true /* callerWillJoin */); + + // Setup hwbinder service + android::sp service = new HdmiCecMock(); + android::status_t status; + status = service->registerAsService(); + LOG_ALWAYS_FATAL_IF(status != android::OK, "Error while registering mock cec service: %d", + status); + + joinRpcThreadpool(); + return 0; +}