1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "android.hardware.tv.hdmi.cec"
18 #include <android-base/logging.h>
19 #include <fcntl.h>
20 #include <utils/Log.h>
21
22 #include <hardware/hardware.h>
23 #include <hardware/hdmi_cec.h>
24 #include "HdmiCecMock.h"
25
26 using ndk::ScopedAStatus;
27
28 namespace android {
29 namespace hardware {
30 namespace tv {
31 namespace hdmi {
32 namespace cec {
33 namespace implementation {
34
serviceDied(void * cookie)35 void HdmiCecMock::serviceDied(void* cookie) {
36 ALOGE("HdmiCecMock died");
37 auto hdmiCecMock = static_cast<HdmiCecMock*>(cookie);
38 hdmiCecMock->mCecThreadRun = false;
39 }
40
addLogicalAddress(CecLogicalAddress addr,Result * _aidl_return)41 ScopedAStatus HdmiCecMock::addLogicalAddress(CecLogicalAddress addr, Result* _aidl_return) {
42 // Have a list to maintain logical addresses
43 mLogicalAddresses.push_back(addr);
44 *_aidl_return = Result::SUCCESS;
45 return ScopedAStatus::ok();
46 }
47
clearLogicalAddress()48 ScopedAStatus HdmiCecMock::clearLogicalAddress() {
49 // Remove logical address from the list
50 mLogicalAddresses = {};
51 return ScopedAStatus::ok();
52 }
53
enableAudioReturnChannel(int32_t portId __unused,bool enable __unused)54 ScopedAStatus HdmiCecMock::enableAudioReturnChannel(int32_t portId __unused, bool enable __unused) {
55 // Maintain ARC status
56 return ScopedAStatus::ok();
57 }
58
getCecVersion(int32_t * _aidl_return)59 ScopedAStatus HdmiCecMock::getCecVersion(int32_t* _aidl_return) {
60 // Maintain a cec version and return it
61 *_aidl_return = mCecVersion;
62 return ScopedAStatus::ok();
63 }
64
getPhysicalAddress(int32_t * _aidl_return)65 ScopedAStatus HdmiCecMock::getPhysicalAddress(int32_t* _aidl_return) {
66 // Maintain a physical address and return it
67 // Default 0xFFFF, update on hotplug event
68 *_aidl_return = mPhysicalAddress;
69 return ScopedAStatus::ok();
70 }
71
getVendorId(int32_t * _aidl_return)72 ScopedAStatus HdmiCecMock::getVendorId(int32_t* _aidl_return) {
73 *_aidl_return = mCecVendorId;
74 return ScopedAStatus::ok();
75 }
76
sendMessage(const CecMessage & message,SendMessageResult * _aidl_return)77 ScopedAStatus HdmiCecMock::sendMessage(const CecMessage& message, SendMessageResult* _aidl_return) {
78 if (message.body.size() == 0) {
79 *_aidl_return = SendMessageResult::NACK;
80 } else {
81 sendMessageToFifo(message);
82 *_aidl_return = SendMessageResult::SUCCESS;
83 }
84 return ScopedAStatus::ok();
85 }
86
setCallback(const std::shared_ptr<IHdmiCecCallback> & callback)87 ScopedAStatus HdmiCecMock::setCallback(const std::shared_ptr<IHdmiCecCallback>& callback) {
88 // If callback is null, mCallback is also set to null so we do not call the old callback.
89 mCallback = callback;
90
91 if (callback != nullptr) {
92 AIBinder_linkToDeath(this->asBinder().get(), mDeathRecipient.get(), 0 /* cookie */);
93
94 mInputFile = open(CEC_MSG_IN_FIFO, O_RDWR | O_CLOEXEC);
95 mOutputFile = open(CEC_MSG_OUT_FIFO, O_RDWR | O_CLOEXEC);
96 pthread_create(&mThreadId, NULL, __threadLoop, this);
97 pthread_setname_np(mThreadId, "hdmi_cec_loop");
98 }
99 return ScopedAStatus::ok();
100 }
101
setLanguage(const std::string & language)102 ScopedAStatus HdmiCecMock::setLanguage(const std::string& language) {
103 if (language.size() != 3) {
104 LOG(ERROR) << "Wrong language code: expected 3 letters, but it was " << language.size()
105 << ".";
106 return ScopedAStatus::ok();
107 }
108 // TODO Validate if language is a valid language code
109 const char* languageStr = language.c_str();
110 int convertedLanguage = ((languageStr[0] & 0xFF) << 16) | ((languageStr[1] & 0xFF) << 8) |
111 (languageStr[2] & 0xFF);
112 mOptionLanguage = convertedLanguage;
113 return ScopedAStatus::ok();
114 }
115
enableWakeupByOtp(bool value)116 ScopedAStatus HdmiCecMock::enableWakeupByOtp(bool value) {
117 mOptionWakeUp = value;
118 return ScopedAStatus::ok();
119 }
120
enableCec(bool value)121 ScopedAStatus HdmiCecMock::enableCec(bool value) {
122 mOptionEnableCec = value;
123 return ScopedAStatus::ok();
124 }
125
enableSystemCecControl(bool value)126 ScopedAStatus HdmiCecMock::enableSystemCecControl(bool value) {
127 mOptionSystemCecControl = value;
128 return ScopedAStatus::ok();
129 }
130
__threadLoop(void * user)131 void* HdmiCecMock::__threadLoop(void* user) {
132 HdmiCecMock* const self = static_cast<HdmiCecMock*>(user);
133 self->threadLoop();
134 return 0;
135 }
136
readMessageFromFifo(unsigned char * buf,int msgCount)137 int HdmiCecMock::readMessageFromFifo(unsigned char* buf, int msgCount) {
138 if (msgCount <= 0 || !buf) {
139 return 0;
140 }
141
142 int ret = -1;
143 // Maybe blocked at driver
144 ret = read(mInputFile, buf, msgCount);
145 if (ret < 0) {
146 ALOGE("[halimp_aidl] read :%s failed, ret:%d\n", CEC_MSG_IN_FIFO, ret);
147 return -1;
148 }
149
150 return ret;
151 }
152
sendMessageToFifo(const CecMessage & message)153 int HdmiCecMock::sendMessageToFifo(const CecMessage& message) {
154 unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH + 1] = {0};
155 int ret = -1;
156
157 msgBuf[0] = ((static_cast<uint8_t>(message.initiator) & 0xf) << 4) |
158 (static_cast<uint8_t>(message.destination) & 0xf);
159
160 size_t length = std::min(static_cast<size_t>(message.body.size()),
161 static_cast<size_t>(CEC_MESSAGE_BODY_MAX_LENGTH));
162 for (size_t i = 0; i < length; ++i) {
163 msgBuf[i + 1] = static_cast<unsigned char>(message.body[i]);
164 }
165
166 // Open the output pipe for writing outgoing cec message
167 mOutputFile = open(CEC_MSG_OUT_FIFO, O_WRONLY | O_CLOEXEC);
168 if (mOutputFile < 0) {
169 ALOGD("[halimp_aidl] file open failed for writing");
170 return -1;
171 }
172
173 // Write message into the output pipe
174 ret = write(mOutputFile, msgBuf, length + 1);
175 close(mOutputFile);
176 if (ret < 0) {
177 ALOGE("[halimp_aidl] write :%s failed, ret:%d\n", CEC_MSG_OUT_FIFO, ret);
178 return -1;
179 }
180 return ret;
181 }
182
printCecMsgBuf(const char * msg_buf,int len)183 void HdmiCecMock::printCecMsgBuf(const char* msg_buf, int len) {
184 int i, size = 0;
185 const int bufSize = CEC_MESSAGE_BODY_MAX_LENGTH * 3;
186 // Use 2 characters for each byte in the message plus 1 space
187 char buf[bufSize] = {0};
188
189 // Messages longer than max length will be truncated.
190 for (i = 0; i < len && size < bufSize; i++) {
191 size += sprintf(buf + size, " %02x", msg_buf[i]);
192 }
193 ALOGD("[halimp_aidl] %s, msg:%.*s", __FUNCTION__, size, buf);
194 }
195
handleCecMessage(unsigned char * msgBuf,int msgSize)196 void HdmiCecMock::handleCecMessage(unsigned char* msgBuf, int msgSize) {
197 CecMessage message;
198 size_t length = std::min(static_cast<size_t>(msgSize - 1),
199 static_cast<size_t>(CEC_MESSAGE_BODY_MAX_LENGTH));
200 message.body.resize(length);
201
202 for (size_t i = 0; i < length; ++i) {
203 message.body[i] = static_cast<uint8_t>(msgBuf[i + 1]);
204 ALOGD("[halimp_aidl] msg body %x", message.body[i]);
205 }
206
207 message.initiator = static_cast<CecLogicalAddress>((msgBuf[0] >> 4) & 0xf);
208 ALOGD("[halimp_aidl] msg init %hhd", message.initiator);
209 message.destination = static_cast<CecLogicalAddress>((msgBuf[0] >> 0) & 0xf);
210 ALOGD("[halimp_aidl] msg dest %hhd", message.destination);
211
212 if (mCallback != nullptr) {
213 mCallback->onCecMessage(message);
214 }
215 }
216
threadLoop()217 void HdmiCecMock::threadLoop() {
218 ALOGD("[halimp_aidl] threadLoop start.");
219 unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH];
220 int r = -1;
221
222 // Open the input pipe
223 while (mInputFile < 0) {
224 usleep(1000 * 1000);
225 mInputFile = open(CEC_MSG_IN_FIFO, O_RDONLY | O_CLOEXEC);
226 }
227 ALOGD("[halimp_aidl] file open ok, fd = %d.", mInputFile);
228
229 while (mCecThreadRun) {
230 if (!mOptionSystemCecControl) {
231 usleep(1000 * 1000);
232 continue;
233 }
234
235 memset(msgBuf, 0, sizeof(msgBuf));
236 // Try to get a message from dev.
237 // echo -n -e '\x04\x83' >> /dev/cec
238 r = readMessageFromFifo(msgBuf, CEC_MESSAGE_BODY_MAX_LENGTH);
239 if (r <= 1) {
240 // Ignore received ping messages
241 continue;
242 }
243
244 printCecMsgBuf((const char*)msgBuf, r);
245
246 if (((msgBuf[0] >> 4) & 0xf) == 0xf) {
247 // The message is a hotplug event, handled by HDMI HAL.
248 continue;
249 }
250
251 handleCecMessage(msgBuf, r);
252 }
253
254 ALOGD("[halimp_aidl] thread end.");
255 }
256
HdmiCecMock()257 HdmiCecMock::HdmiCecMock() {
258 ALOGE("[halimp_aidl] Opening a virtual CEC HAL for testing and virtual machine.");
259 mCallback = nullptr;
260 mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(AIBinder_DeathRecipient_new(serviceDied));
261 }
262
263 } // namespace implementation
264 } // namespace cec
265 } // namespace hdmi
266 } // namespace tv
267 } // namespace hardware
268 } // namespace android
269