Commit | Line | Data |
---|---|---|
9df09228 DW |
1 | /* |
2 | * Copyright (C) 2019, The LineageOS 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 | /* | |
18 | * Camera HAL "CallbackWorkerThread" workaround description | |
19 | * | |
20 | * The camera HAL of the Exynos7580 (A3, A5, ... 2016) reguralry deadlocks when using | |
21 | * most camera apps, this happens a lot when using Google Camera but does occur | |
22 | * occasionally in Snap. | |
23 | * | |
24 | * The issue was tracked down to the way that Samsung operates their HAL, all operations | |
25 | * are run in multiple threads and the different callbacks are executed from one of these | |
26 | * threads. | |
27 | * | |
28 | * The deadlocks occur when the camera client executes a function like cancel_auto_focus | |
29 | * or stop_preview and a callback from the HAL occurs at around the same time. The HAL | |
30 | * waits for the callback functions to return before they finish processing the client | |
31 | * calls and the client stops processing callbacks until their calling function completes. | |
32 | * | |
33 | * You end up in a state when both the HAL and the client are waiting for the other | |
34 | * process to finish and so end up with a deadlock of the HAL/Client which only a reboot | |
35 | * can cure. | |
36 | * | |
37 | * This worker thread offloads the actual callbacks to another thread which allows the | |
38 | * HAL to finish the client calls and avoid the deadlock scenario. | |
39 | * | |
40 | */ | |
443860a7 DW |
41 | |
42 | #define LOG_NDEBUG 0 | |
43 | #define LOG_TAG "Camera2WrapperCbThread" | |
44 | ||
45 | #include "CallbackWorkerThread.h" | |
46 | #include <iostream> | |
47 | #include <cutils/log.h> | |
48 | ||
49 | using namespace std; | |
50 | ||
51 | #define MSG_EXIT_THREAD 1 | |
52 | #define MSG_EXECUTE_CALLBACK 2 | |
53 | #define MSG_UPDATE_CALLBACKS 3 | |
54 | ||
55 | struct ThreadMsg | |
56 | { | |
57 | ThreadMsg(int i, const void* m, long long ts) { id = i; msg = m; CallerTS = ts; } | |
58 | int id; | |
59 | const void* msg; | |
60 | long long CallerTS; | |
61 | }; | |
62 | ||
63 | CallbackWorkerThread::CallbackWorkerThread() : m_thread(0) { | |
64 | } | |
65 | ||
66 | CallbackWorkerThread::~CallbackWorkerThread() { | |
67 | ExitThread(); | |
68 | } | |
69 | ||
70 | bool CallbackWorkerThread::CreateThread() { | |
71 | if (!m_thread) | |
72 | m_thread = new thread(&CallbackWorkerThread::Process, this); | |
73 | return true; | |
74 | } | |
75 | ||
76 | void CallbackWorkerThread::ExitThread() { | |
77 | if (!m_thread) | |
78 | return; | |
79 | ||
80 | /* Create the exit thread worker message */ | |
81 | ThreadMsg* threadMsg = new ThreadMsg(MSG_EXIT_THREAD, 0, GetTimestamp()); | |
82 | ||
83 | /* Add it to the message queue */ | |
84 | { | |
85 | lock_guard<mutex> lock(m_mutex); | |
86 | m_queue.push(threadMsg); | |
87 | m_cv.notify_one(); | |
88 | } | |
89 | ||
90 | /* Join the thread and then cleanup */ | |
91 | m_thread->join(); | |
92 | delete m_thread; | |
93 | m_thread = 0; | |
94 | } | |
95 | ||
96 | void CallbackWorkerThread::AddCallback(const WorkerMessage* data) { | |
97 | /* Assert that the thread exists */ | |
9df09228 | 98 | ALOG_ASSERT(m_thread != NULL); |
443860a7 DW |
99 | |
100 | /* Create a new worker thread message from the data */ | |
101 | ThreadMsg* threadMsg = new ThreadMsg(MSG_EXECUTE_CALLBACK, data, GetTimestamp()); | |
102 | ||
103 | /* Add it to our worker queue and notify the worker */ | |
104 | std::unique_lock<std::mutex> lk(m_mutex); | |
105 | m_queue.push(threadMsg); | |
106 | m_cv.notify_one(); | |
107 | } | |
108 | ||
109 | void CallbackWorkerThread::SetCallbacks(const CallbackData* data) { | |
110 | /* Assert that the thread exists */ | |
9df09228 | 111 | ALOG_ASSERT(m_thread != NULL); |
443860a7 DW |
112 | |
113 | /* Create a new worker thread message from the callback data */ | |
114 | ThreadMsg* threadMsg = new ThreadMsg(MSG_UPDATE_CALLBACKS, data, GetTimestamp()); | |
115 | ||
116 | /* Add it to our worker queue and notify the worker */ | |
117 | std::unique_lock<std::mutex> lk(m_mutex); | |
118 | m_queue.push(threadMsg); | |
119 | m_cv.notify_one(); | |
120 | } | |
121 | ||
122 | void CallbackWorkerThread::ClearCallbacks() { | |
123 | /* Assert that the thread exists */ | |
9df09228 | 124 | ALOG_ASSERT(m_thread != NULL); |
443860a7 DW |
125 | |
126 | /* Lock the mutex and clear the message queue */ | |
127 | std::unique_lock<std::mutex> lk(m_mutex); | |
128 | ||
129 | ALOGV("%s: Clearing %i messages", __FUNCTION__, m_queue.size()); | |
130 | ||
131 | /* Whilst the queue is not empty */ | |
132 | while (!m_queue.empty()) { | |
133 | /* Pop the message from the queue and delete the allocated data */ | |
134 | ThreadMsg* msg = m_queue.front(); | |
135 | m_queue.pop(); | |
136 | delete msg; | |
137 | } | |
138 | ||
139 | m_cv.notify_one(); | |
140 | } | |
141 | ||
142 | void CallbackWorkerThread::Process() { | |
143 | camera_notify_callback UserNotifyCb = NULL; | |
144 | camera_data_callback UserDataCb = NULL; | |
145 | ||
146 | while (1) { | |
147 | ThreadMsg* msg = 0; | |
148 | { | |
149 | /* Wait for a message to be added to the queue */ | |
150 | std::unique_lock<std::mutex> lk(m_mutex); | |
151 | while (m_queue.empty()) | |
152 | m_cv.wait(lk); | |
153 | ||
154 | if (m_queue.empty()) | |
155 | continue; | |
156 | ||
157 | msg = m_queue.front(); | |
158 | m_queue.pop(); | |
159 | } | |
160 | ||
161 | switch (msg->id) { | |
162 | case MSG_EXECUTE_CALLBACK: | |
163 | { | |
164 | /* Assert that we have a valid message */ | |
9df09228 | 165 | ALOG_ASSERT(msg->msg != NULL); |
443860a7 DW |
166 | |
167 | /* Cast the the ThreadMsg void* data back to a WorkerMessage* */ | |
168 | const WorkerMessage* userData = static_cast<const WorkerMessage*>(msg->msg); | |
169 | ||
170 | /* If the callback is not stale (newer than 5mS) */ | |
171 | if(GetTimestamp() - msg->CallerTS < 5) { | |
172 | /* If the callback type is set to notifycb */ | |
173 | if(userData->CbType == CB_TYPE_NOTIFY) { | |
174 | /* Execute the users notify callback if it is valid */ | |
175 | if(UserNotifyCb != NULL) { | |
176 | ALOGV("%s: UserNotifyCb: %i %i %i %p", __FUNCTION__, userData->msg_type, userData->ext1, userData->ext2, userData->user); | |
177 | UserNotifyCb(userData->msg_type, userData->ext1, userData->ext2, userData->user); | |
178 | } | |
179 | } /* If the callback type is set to notifycb */ | |
180 | else if(userData->CbType == CB_TYPE_DATA) { | |
181 | /* Execute the users data callback if it is valid */ | |
182 | if(UserDataCb != NULL) { | |
183 | ALOGV("%s: UserDataCb: %i %p %i %p %p", __FUNCTION__, userData->msg_type, userData->data, userData->index, userData->metadata, userData->user); | |
184 | UserDataCb(userData->msg_type, userData->data, userData->index, userData->metadata, userData->user); | |
185 | } | |
186 | } | |
187 | } else { | |
188 | /* If the callback type is set to notifycb */ | |
189 | if(userData->CbType == CB_TYPE_NOTIFY) { | |
190 | ALOGV("%s: UserNotifyCb Stale: %llimS old", __FUNCTION__, GetTimestamp() - msg->CallerTS); | |
191 | } /* If the callback type is set to notifycb */ | |
192 | else if(userData->CbType == CB_TYPE_DATA) { | |
193 | ALOGV("%s: UserDataCb Stale: %llimS old", __FUNCTION__, GetTimestamp() - msg->CallerTS); | |
194 | } | |
195 | } | |
196 | ||
197 | /* Cleanup allocated data */ | |
198 | delete userData; | |
199 | delete msg; | |
200 | break; | |
201 | } | |
202 | ||
203 | case MSG_UPDATE_CALLBACKS: | |
204 | { | |
205 | /* Assert that we have a valid message */ | |
9df09228 | 206 | ALOG_ASSERT(msg->msg != NULL); |
443860a7 DW |
207 | |
208 | /* Cast the the ThreadMsg void* data back to a CallbackData* */ | |
209 | const CallbackData* callbackData = static_cast<const CallbackData*>(msg->msg); | |
210 | ||
211 | ALOGV("%s: UpdateCallbacks", __FUNCTION__); | |
212 | ||
213 | /* Copy the new callback pointers */ | |
214 | UserNotifyCb = callbackData->NewUserNotifyCb; | |
215 | UserDataCb = callbackData->NewUserDataCb; | |
216 | ||
217 | /* Cleanup allocated data */ | |
218 | delete callbackData; | |
219 | delete msg; | |
220 | break; | |
221 | } | |
222 | ||
223 | case MSG_EXIT_THREAD: | |
224 | { | |
225 | /* Delete current message */ | |
226 | delete msg; | |
227 | /* Then delete all pending messages in the queue */ | |
228 | std::unique_lock<std::mutex> lk(m_mutex); | |
229 | /* Whilst the queue is not empty */ | |
230 | while (!m_queue.empty()) { | |
231 | /* Pop the message from the queue and delete the allocated data */ | |
232 | msg = m_queue.front(); | |
233 | m_queue.pop(); | |
234 | delete msg; | |
235 | } | |
236 | ||
237 | ALOGV("%s: Exit Thread", __FUNCTION__); | |
238 | return; | |
239 | } | |
240 | ||
241 | default: | |
242 | /* Error if we get here */ | |
9df09228 | 243 | ALOG_ASSERT(0); |
443860a7 DW |
244 | } |
245 | } | |
246 | } | |
247 | ||
248 | ||
249 | /* based on current_timestamp() function from stack overflow: | |
250 | * https://stackoverflow.com/questions/3756323/how-to-get-the-current-time-in-milliseconds-from-c-in-linux/17083824 | |
251 | */ | |
252 | ||
253 | long long CallbackWorkerThread::GetTimestamp() { | |
254 | struct timeval te; | |
255 | gettimeofday(&te, NULL); // get current time | |
256 | long long milliseconds = te.tv_sec*1000LL + te.tv_usec/1000; // calculate milliseconds | |
257 | return milliseconds; | |
258 | } | |
259 |