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