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