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