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