1 /*
2 * Copyright (C) 2021 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.cec@1.1"
18 #include <android-base/logging.h>
19 #include <utils/Log.h>
20
21 #include <hardware/hardware.h>
22 #include <hardware/hdmi_cec.h>
23 #include "HdmiCecMock.h"
24
25 namespace android {
26 namespace hardware {
27 namespace tv {
28 namespace cec {
29 namespace V1_1 {
30 namespace implementation {
31
32 class WrappedCallback : public ::android::hardware::tv::cec::V1_1::IHdmiCecCallback {
33 public:
WrappedCallback(sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback> callback)34 WrappedCallback(sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback> callback) {
35 mCallback = callback;
36 }
37
onCecMessage(const::android::hardware::tv::cec::V1_0::CecMessage & message)38 Return<void> onCecMessage(const ::android::hardware::tv::cec::V1_0::CecMessage& message) {
39 mCallback->onCecMessage(message);
40 return Void();
41 }
onCecMessage_1_1(const::android::hardware::tv::cec::V1_1::CecMessage & message)42 Return<void> onCecMessage_1_1(const ::android::hardware::tv::cec::V1_1::CecMessage& message) {
43 ::android::hardware::tv::cec::V1_0::CecMessage cecMessage;
44 cecMessage.initiator =
45 ::android::hardware::tv::cec::V1_0::CecLogicalAddress(message.initiator);
46 cecMessage.destination =
47 ::android::hardware::tv::cec::V1_0::CecLogicalAddress(message.destination);
48 cecMessage.body = message.body;
49 mCallback->onCecMessage(cecMessage);
50 return Void();
51 }
onHotplugEvent(const::android::hardware::tv::cec::V1_0::HotplugEvent & event)52 Return<void> onHotplugEvent(const ::android::hardware::tv::cec::V1_0::HotplugEvent& event) {
53 mCallback->onHotplugEvent(event);
54 return Void();
55 }
56
57 private:
58 sp<::android::hardware::tv::cec::V1_0::IHdmiCecCallback> mCallback;
59 };
60
61 /*
62 * (*set_option)() passes flags controlling the way HDMI-CEC service works down
63 * to HAL implementation. Those flags will be used in case the feature needs
64 * update in HAL itself, firmware or microcontroller.
65 */
cec_set_option(int flag,int value)66 void HdmiCecMock::cec_set_option(int flag, int value) {
67 // maintain options and set them accordingly
68 switch (flag) {
69 case HDMI_OPTION_WAKEUP:
70 mOptionWakeUp = value;
71 break;
72 case HDMI_OPTION_ENABLE_CEC:
73 mOptionEnableCec = value;
74 break;
75 case HDMI_OPTION_SYSTEM_CEC_CONTROL:
76 mOptionSystemCecControl = value;
77 break;
78 case HDMI_OPTION_SET_LANG:
79 mOptionLanguage = value;
80 break;
81 }
82 }
83
84 // Methods from ::android::hardware::tv::cec::V1_0::IHdmiCec follow.
addLogicalAddress(CecLogicalAddress addr)85 Return<Result> HdmiCecMock::addLogicalAddress(CecLogicalAddress addr) {
86 return addLogicalAddress_1_1(::android::hardware::tv::cec::V1_1::CecLogicalAddress(addr));
87 }
88
clearLogicalAddress()89 Return<void> HdmiCecMock::clearLogicalAddress() {
90 // remove logical address from the list
91 mLogicalAddresses = {};
92 return Void();
93 }
94
getPhysicalAddress(getPhysicalAddress_cb _hidl_cb)95 Return<void> HdmiCecMock::getPhysicalAddress(getPhysicalAddress_cb _hidl_cb) {
96 // maintain a physical address and return it
97 // default 0xFFFF, update on hotplug event
98 _hidl_cb(Result::SUCCESS, mPhysicalAddress);
99 return Void();
100 }
101
sendMessage(const CecMessage & message)102 Return<SendMessageResult> HdmiCecMock::sendMessage(const CecMessage& message) {
103 ::android::hardware::tv::cec::V1_1::CecMessage cecMessage;
104 cecMessage.initiator = ::android::hardware::tv::cec::V1_1::CecLogicalAddress(message.initiator);
105 cecMessage.destination =
106 ::android::hardware::tv::cec::V1_1::CecLogicalAddress(message.destination);
107 cecMessage.body = message.body;
108 return sendMessage_1_1(cecMessage);
109 }
110
setCallback(const sp<IHdmiCecCallback> & callback)111 Return<void> HdmiCecMock::setCallback(const sp<IHdmiCecCallback>& callback) {
112 return setCallback_1_1(new WrappedCallback(callback));
113 }
114
getCecVersion()115 Return<int32_t> HdmiCecMock::getCecVersion() {
116 // maintain a cec version and return it
117 return mCecVersion;
118 }
119
getVendorId()120 Return<uint32_t> HdmiCecMock::getVendorId() {
121 return mCecVendorId;
122 }
123
getPortInfo(getPortInfo_cb _hidl_cb)124 Return<void> HdmiCecMock::getPortInfo(getPortInfo_cb _hidl_cb) {
125 // TODO ready port info from device specific config
126 _hidl_cb(mPortInfo);
127 return Void();
128 }
129
setOption(OptionKey key,bool value)130 Return<void> HdmiCecMock::setOption(OptionKey key, bool value) {
131 cec_set_option(static_cast<int>(key), value ? 1 : 0);
132 return Void();
133 }
134
setLanguage(const hidl_string & language)135 Return<void> HdmiCecMock::setLanguage(const hidl_string& language) {
136 if (language.size() != 3) {
137 LOG(ERROR) << "Wrong language code: expected 3 letters, but it was " << language.size()
138 << ".";
139 return Void();
140 }
141 // TODO validate if language is a valid language code
142 const char* languageStr = language.c_str();
143 int convertedLanguage = ((languageStr[0] & 0xFF) << 16) | ((languageStr[1] & 0xFF) << 8) |
144 (languageStr[2] & 0xFF);
145 cec_set_option(HDMI_OPTION_SET_LANG, convertedLanguage);
146 return Void();
147 }
148
enableAudioReturnChannel(int32_t portId __unused,bool enable __unused)149 Return<void> HdmiCecMock::enableAudioReturnChannel(int32_t portId __unused, bool enable __unused) {
150 // Maintain ARC status
151 return Void();
152 }
153
isConnected(int32_t portId)154 Return<bool> HdmiCecMock::isConnected(int32_t portId) {
155 // maintain port connection status and update on hotplug event
156 if (portId < mTotalPorts && portId >= 0) {
157 return mPortConnectionStatus[portId];
158 }
159 return false;
160 }
161
162 // Methods from ::android::hardware::tv::cec::V1_1::IHdmiCec follow.
addLogicalAddress_1_1(::android::hardware::tv::cec::V1_1::CecLogicalAddress addr)163 Return<Result> HdmiCecMock::addLogicalAddress_1_1(
164 ::android::hardware::tv::cec::V1_1::CecLogicalAddress addr) {
165 // have a list to maintain logical addresses
166 int size = mLogicalAddresses.size();
167 mLogicalAddresses.resize(size + 1);
168 mLogicalAddresses[size + 1] = addr;
169 return Result::SUCCESS;
170 }
171
sendMessage_1_1(const::android::hardware::tv::cec::V1_1::CecMessage & message)172 Return<SendMessageResult> HdmiCecMock::sendMessage_1_1(
173 const ::android::hardware::tv::cec::V1_1::CecMessage& message) {
174 if (message.body.size() == 0) {
175 return SendMessageResult::NACK;
176 }
177 sendMessageToFifo(message);
178 return SendMessageResult::SUCCESS;
179 }
180
setCallback_1_1(const sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback> & callback)181 Return<void> HdmiCecMock::setCallback_1_1(
182 const sp<::android::hardware::tv::cec::V1_1::IHdmiCecCallback>& callback) {
183 if (mCallback != nullptr) {
184 mCallback = nullptr;
185 }
186
187 if (callback != nullptr) {
188 mCallback = callback;
189 mCallback->linkToDeath(this, 0 /*cookie*/);
190
191 mInputFile = open(CEC_MSG_IN_FIFO, O_RDWR);
192 mOutputFile = open(CEC_MSG_OUT_FIFO, O_RDWR);
193 pthread_create(&mThreadId, NULL, __threadLoop, this);
194 pthread_setname_np(mThreadId, "hdmi_cec_loop");
195 }
196 return Void();
197 }
198
__threadLoop(void * user)199 void* HdmiCecMock::__threadLoop(void* user) {
200 HdmiCecMock* const self = static_cast<HdmiCecMock*>(user);
201 self->threadLoop();
202 return 0;
203 }
204
readMessageFromFifo(unsigned char * buf,int msgCount)205 int HdmiCecMock::readMessageFromFifo(unsigned char* buf, int msgCount) {
206 if (msgCount <= 0 || !buf) {
207 return 0;
208 }
209
210 int ret = -1;
211 /* maybe blocked at driver */
212 ret = read(mInputFile, buf, msgCount);
213 if (ret < 0) {
214 ALOGE("[halimp] read :%s failed, ret:%d\n", CEC_MSG_IN_FIFO, ret);
215 return -1;
216 }
217
218 return ret;
219 }
220
sendMessageToFifo(const::android::hardware::tv::cec::V1_1::CecMessage & message)221 int HdmiCecMock::sendMessageToFifo(const ::android::hardware::tv::cec::V1_1::CecMessage& message) {
222 unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH];
223 int ret = -1;
224
225 memset(msgBuf, 0, sizeof(msgBuf));
226 msgBuf[0] = ((static_cast<uint8_t>(message.initiator) & 0xf) << 4) |
227 (static_cast<uint8_t>(message.destination) & 0xf);
228
229 size_t length = std::min(static_cast<size_t>(message.body.size()),
230 static_cast<size_t>(MaxLength::MESSAGE_BODY));
231 for (size_t i = 0; i < length; ++i) {
232 msgBuf[i + 1] = static_cast<unsigned char>(message.body[i]);
233 }
234
235 // open the output pipe for writing outgoing cec message
236 mOutputFile = open(CEC_MSG_OUT_FIFO, O_WRONLY);
237 if (mOutputFile < 0) {
238 ALOGD("[halimp] file open failed for writing");
239 return -1;
240 }
241
242 // write message into the output pipe
243 ret = write(mOutputFile, msgBuf, length + 1);
244 close(mOutputFile);
245 if (ret < 0) {
246 ALOGE("[halimp] write :%s failed, ret:%d\n", CEC_MSG_OUT_FIFO, ret);
247 return -1;
248 }
249 return ret;
250 }
251
printCecMsgBuf(const char * msg_buf,int len)252 void HdmiCecMock::printCecMsgBuf(const char* msg_buf, int len) {
253 char buf[64] = {};
254 int i, size = 0;
255 memset(buf, 0, sizeof(buf));
256 for (i = 0; i < len; i++) {
257 size += sprintf(buf + size, " %02x", msg_buf[i]);
258 }
259 ALOGD("[halimp] %s, msg:%s", __FUNCTION__, buf);
260 }
261
handleHotplugMessage(unsigned char * msgBuf)262 void HdmiCecMock::handleHotplugMessage(unsigned char* msgBuf) {
263 HotplugEvent hotplugEvent{.connected = ((msgBuf[3]) & 0xf) > 0,
264 .portId = static_cast<uint32_t>(msgBuf[0] & 0xf)};
265
266 if (hotplugEvent.portId >= mPortInfo.size()) {
267 ALOGD("[halimp] ignore hot plug message, id %x does not exist", hotplugEvent.portId);
268 return;
269 }
270
271 ALOGD("[halimp] hot plug port id %x, is connected %x", (msgBuf[0] & 0xf), (msgBuf[3] & 0xf));
272 if (mPortInfo[hotplugEvent.portId].type == HdmiPortType::OUTPUT) {
273 mPhysicalAddress =
274 ((hotplugEvent.connected == 0) ? 0xffff : ((msgBuf[1] << 8) | (msgBuf[2])));
275 mPortInfo[hotplugEvent.portId].physicalAddress = mPhysicalAddress;
276 ALOGD("[halimp] hot plug physical address %x", mPhysicalAddress);
277 }
278
279 // todo update connection status
280
281 if (mCallback != nullptr) {
282 mCallback->onHotplugEvent(hotplugEvent);
283 }
284 }
285
handleCecMessage(unsigned char * msgBuf,int megSize)286 void HdmiCecMock::handleCecMessage(unsigned char* msgBuf, int megSize) {
287 ::android::hardware::tv::cec::V1_1::CecMessage message;
288 size_t length = std::min(static_cast<size_t>(megSize - 1),
289 static_cast<size_t>(MaxLength::MESSAGE_BODY));
290 message.body.resize(length);
291
292 for (size_t i = 0; i < length; ++i) {
293 message.body[i] = static_cast<uint8_t>(msgBuf[i + 1]);
294 ALOGD("[halimp] msg body %x", message.body[i]);
295 }
296
297 message.initiator = static_cast<::android::hardware::tv::cec::V1_1::CecLogicalAddress>(
298 (msgBuf[0] >> 4) & 0xf);
299 ALOGD("[halimp] msg init %x", message.initiator);
300 message.destination = static_cast<::android::hardware::tv::cec::V1_1::CecLogicalAddress>(
301 (msgBuf[0] >> 0) & 0xf);
302 ALOGD("[halimp] msg dest %x", message.destination);
303
304 // messageValidateAndHandle(&event);
305
306 if (mCallback != nullptr) {
307 mCallback->onCecMessage_1_1(message);
308 }
309 }
310
threadLoop()311 void HdmiCecMock::threadLoop() {
312 ALOGD("[halimp] threadLoop start.");
313 unsigned char msgBuf[CEC_MESSAGE_BODY_MAX_LENGTH];
314 int r = -1;
315
316 // open the input pipe
317 while (mInputFile < 0) {
318 usleep(1000 * 1000);
319 mInputFile = open(CEC_MSG_IN_FIFO, O_RDONLY);
320 }
321 ALOGD("[halimp] file open ok, fd = %d.", mInputFile);
322
323 while (mCecThreadRun) {
324 if (!mOptionSystemCecControl) {
325 usleep(1000 * 1000);
326 continue;
327 }
328
329 memset(msgBuf, 0, sizeof(msgBuf));
330 // try to get a message from dev.
331 // echo -n -e '\x04\x83' >> /dev/cec
332 r = readMessageFromFifo(msgBuf, CEC_MESSAGE_BODY_MAX_LENGTH);
333 if (r <= 1) {
334 // ignore received ping messages
335 continue;
336 }
337
338 printCecMsgBuf((const char*)msgBuf, r);
339
340 if (((msgBuf[0] >> 4) & 0xf) == 0xf) {
341 // the message is a hotplug event
342 handleHotplugMessage(msgBuf);
343 continue;
344 }
345
346 handleCecMessage(msgBuf, r);
347 }
348
349 ALOGD("[halimp] thread end.");
350 // mCecDevice.mExited = true;
351 }
352
HdmiCecMock()353 HdmiCecMock::HdmiCecMock() {
354 ALOGE("[halimp] Opening a virtual HAL for testing and virtual machine.");
355 mCallback = nullptr;
356 mPortInfo.resize(mTotalPorts);
357 mPortConnectionStatus.resize(mTotalPorts);
358 mPortInfo[0] = {.type = HdmiPortType::OUTPUT,
359 .portId = static_cast<uint32_t>(1),
360 .cecSupported = true,
361 .arcSupported = false,
362 .physicalAddress = mPhysicalAddress};
363 mPortConnectionStatus[0] = false;
364 }
365
366 } // namespace implementation
367 } // namespace V1_1
368 } // namespace cec
369 } // namespace tv
370 } // namespace hardware
371 } // namespace android