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