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