exynos9610: Usb: HAL: Update wahoo HAL
[GitHub/moto-9609/android_device_motorola_exynos9610-common.git] / hidl / usb / Usb.cpp
1 /*
2 * Copyright (C) 2017 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.usb@1.1-service.wahoo"
18
19 #include <android-base/logging.h>
20 #include <assert.h>
21 #include <chrono>
22 #include <dirent.h>
23 #include <pthread.h>
24 #include <regex>
25 #include <stdio.h>
26 #include <sys/types.h>
27 #include <thread>
28 #include <unistd.h>
29 #include <unordered_map>
30
31 #include <cutils/uevent.h>
32 #include <sys/epoll.h>
33 #include <utils/Errors.h>
34 #include <utils/StrongPointer.h>
35
36 #include "Usb.h"
37
38 namespace android {
39 namespace hardware {
40 namespace usb {
41 namespace V1_1 {
42 namespace implementation {
43
44 // Set by the signal handler to destroy the thread
45 volatile bool destroyThread;
46
47 int32_t readFile(const std::string &filename, std::string *contents) {
48 FILE *fp;
49 ssize_t read = 0;
50 char *line = NULL;
51 size_t len = 0;
52
53 fp = fopen(filename.c_str(), "r");
54 if (fp != NULL) {
55 if ((read = getline(&line, &len, fp)) != -1) {
56 char *pos;
57 if ((pos = strchr(line, '\n')) != NULL) *pos = '\0';
58 *contents = line;
59 }
60 free(line);
61 fclose(fp);
62 return 0;
63 } else {
64 ALOGE("fopen failed");
65 }
66
67 return -1;
68 }
69
70 std::string appendRoleNodeHelper(const std::string &portName,
71 PortRoleType type) {
72 std::string node("/sys/class/typec/" + portName);
73
74 switch (type) {
75 case PortRoleType::DATA_ROLE:
76 return node + "/data_role";
77 case PortRoleType::POWER_ROLE:
78 return node + "/power_role";
79 case PortRoleType::MODE:
80 return node + "/port_type";
81 default:
82 return "";
83 }
84 }
85
86 std::string convertRoletoString(PortRole role) {
87 if (role.type == PortRoleType::POWER_ROLE) {
88 if (role.role == static_cast<uint32_t>(PortPowerRole::SOURCE))
89 return "source";
90 else if (role.role == static_cast<uint32_t>(PortPowerRole::SINK))
91 return "sink";
92 } else if (role.type == PortRoleType::DATA_ROLE) {
93 if (role.role == static_cast<uint32_t>(PortDataRole::HOST)) return "host";
94 if (role.role == static_cast<uint32_t>(PortDataRole::DEVICE))
95 return "device";
96 } else if (role.type == PortRoleType::MODE) {
97 if (role.role == static_cast<uint32_t>(PortMode_1_1::UFP)) return "sink";
98 if (role.role == static_cast<uint32_t>(PortMode_1_1::DFP)) return "source";
99 }
100 return "none";
101 }
102
103 void extractRole(std::string *roleName) {
104 std::size_t first, last;
105
106 first = roleName->find("[");
107 last = roleName->find("]");
108
109 if (first != std::string::npos && last != std::string::npos) {
110 *roleName = roleName->substr(first + 1, last - first - 1);
111 }
112 }
113
114 void switchToDrp(const std::string &portName) {
115 std::string filename =
116 appendRoleNodeHelper(std::string(portName.c_str()), PortRoleType::MODE);
117 FILE *fp;
118
119 if (filename != "") {
120 fp = fopen(filename.c_str(), "w");
121 if (fp != NULL) {
122 int ret = fputs("dual", fp);
123 fclose(fp);
124 if (ret == EOF)
125 ALOGE("Fatal: Error while switching back to drp");
126 } else {
127 ALOGE("Fatal: Cannot open file to switch back to drp");
128 }
129 } else {
130 ALOGE("Fatal: invalid node type");
131 }
132 }
133
134 bool switchMode(const hidl_string &portName,
135 const PortRole &newRole, struct Usb *usb) {
136 std::string filename =
137 appendRoleNodeHelper(std::string(portName.c_str()), newRole.type);
138 std::string written;
139 FILE *fp;
140 bool roleSwitch = false;
141
142 if (filename == "") {
143 ALOGE("Fatal: invalid node type");
144 return false;
145 }
146
147 fp = fopen(filename.c_str(), "w");
148 if (fp != NULL) {
149 // Hold the lock here to prevent loosing connected signals
150 // as once the file is written the partner added signal
151 // can arrive anytime.
152 pthread_mutex_lock(&usb->mPartnerLock);
153 usb->mPartnerUp = false;
154 int ret = fputs(convertRoletoString(newRole).c_str(), fp);
155 fclose(fp);
156
157 if (ret != EOF) {
158 struct timespec to;
159 struct timeval tp;
160
161 wait_again:
162 gettimeofday(&tp, NULL);
163 to.tv_sec = tp.tv_sec + PORT_TYPE_TIMEOUT;
164 to.tv_nsec = tp.tv_usec * 1000;;
165
166 int err = pthread_cond_timedwait(&usb->mPartnerCV, &usb->mPartnerLock, &to);
167 // There are no uevent signals which implies role swap timed out.
168 if (err == ETIMEDOUT) {
169 ALOGI("uevents wait timedout");
170 // Sanity check.
171 } else if (!usb->mPartnerUp) {
172 goto wait_again;
173 // Role switch succeeded since usb->mPartnerUp is true.
174 } else {
175 roleSwitch = true;
176 }
177 } else {
178 ALOGI("Role switch failed while wrting to file");
179 }
180 pthread_mutex_unlock(&usb->mPartnerLock);
181 }
182
183 if (!roleSwitch)
184 switchToDrp(std::string(portName.c_str()));
185
186 return roleSwitch;
187 }
188
189
190
191 Return<void> Usb::switchRole(const hidl_string &portName,
192 const PortRole &newRole) {
193 std::string filename =
194 appendRoleNodeHelper(std::string(portName.c_str()), newRole.type);
195 std::string written;
196 FILE *fp;
197 bool roleSwitch = false;
198
199 if (filename == "") {
200 ALOGE("Fatal: invalid node type");
201 return Void();
202 }
203
204 pthread_mutex_lock(&mRoleSwitchLock);
205
206 ALOGI("filename write: %s role:%s", filename.c_str(),
207 convertRoletoString(newRole).c_str());
208
209 if (newRole.type == PortRoleType::MODE) {
210 roleSwitch = switchMode(portName, newRole, this);
211 } else {
212 fp = fopen(filename.c_str(), "w");
213 if (fp != NULL) {
214 int ret = fputs(convertRoletoString(newRole).c_str(), fp);
215 fclose(fp);
216 if ((ret != EOF) && !readFile(filename, &written)) {
217 extractRole(&written);
218 ALOGI("written: %s", written.c_str());
219 if (written == convertRoletoString(newRole)) {
220 roleSwitch = true;
221 } else {
222 ALOGE("Role switch failed");
223 }
224 } else {
225 ALOGE("failed to update the new role");
226 }
227 } else {
228 ALOGE("fopen failed");
229 }
230 }
231
232 pthread_mutex_lock(&mLock);
233 if (mCallback_1_0 != NULL) {
234 Return<void> ret =
235 mCallback_1_0->notifyRoleSwitchStatus(portName, newRole,
236 roleSwitch ? Status::SUCCESS : Status::ERROR);
237 if (!ret.isOk())
238 ALOGE("RoleSwitchStatus error %s", ret.description().c_str());
239 } else {
240 ALOGE("Not notifying the userspace. Callback is not set");
241 }
242 pthread_mutex_unlock(&mLock);
243 pthread_mutex_unlock(&mRoleSwitchLock);
244
245 return Void();
246 }
247
248 Status getAccessoryConnected(const std::string &portName, std::string *accessory) {
249 std::string filename =
250 "/sys/class/typec/" + portName + "-partner/accessory_mode";
251
252 if (readFile(filename, accessory)) {
253 ALOGE("getAccessoryConnected: Failed to open filesystem node: %s",
254 filename.c_str());
255 return Status::ERROR;
256 }
257
258 return Status::SUCCESS;
259 }
260
261 Status getCurrentRoleHelper(const std::string &portName, bool connected,
262 PortRoleType type, uint32_t *currentRole) {
263 std::string filename;
264 std::string roleName;
265 std::string accessory;
266
267 // Mode
268
269 if (type == PortRoleType::POWER_ROLE) {
270 filename = "/sys/class/typec/" + portName + "/power_role";
271 *currentRole = static_cast<uint32_t>(PortPowerRole::NONE);
272 } else if (type == PortRoleType::DATA_ROLE) {
273 filename = "/sys/class/typec/" + portName + "/data_role";
274 *currentRole = static_cast<uint32_t>(PortDataRole::NONE);
275 } else if (type == PortRoleType::MODE) {
276 filename = "/sys/class/typec/" + portName + "/data_role";
277 *currentRole = static_cast<uint32_t>(PortMode_1_1::NONE);
278 } else {
279 return Status::ERROR;
280 }
281
282 if (!connected) return Status::SUCCESS;
283
284 if (type == PortRoleType::MODE) {
285 if (getAccessoryConnected(portName, &accessory) != Status::SUCCESS) {
286 return Status::ERROR;
287 }
288 if (accessory == "analog_audio") {
289 *currentRole = static_cast<uint32_t>(PortMode_1_1::AUDIO_ACCESSORY);
290 return Status::SUCCESS;
291 } else if (accessory == "debug") {
292 *currentRole = static_cast<uint32_t>(PortMode_1_1::DEBUG_ACCESSORY);
293 return Status::SUCCESS;
294 }
295 }
296
297 if (readFile(filename, &roleName)) {
298 ALOGE("getCurrentRole: Failed to open filesystem node: %s",
299 filename.c_str());
300 return Status::ERROR;
301 }
302
303 extractRole(&roleName);
304
305 if (roleName == "source") {
306 *currentRole = static_cast<uint32_t>(PortPowerRole::SOURCE);
307 } else if (roleName == "sink") {
308 *currentRole = static_cast<uint32_t>(PortPowerRole::SINK);
309 } else if (roleName == "host") {
310 if (type == PortRoleType::DATA_ROLE)
311 *currentRole = static_cast<uint32_t>(PortDataRole::HOST);
312 else
313 *currentRole = static_cast<uint32_t>(PortMode_1_1::DFP);
314 } else if (roleName == "device") {
315 if (type == PortRoleType::DATA_ROLE)
316 *currentRole = static_cast<uint32_t>(PortDataRole::DEVICE);
317 else
318 *currentRole = static_cast<uint32_t>(PortMode_1_1::UFP);
319 } else if (roleName != "none") {
320 /* case for none has already been addressed.
321 * so we check if the role isnt none.
322 */
323 return Status::UNRECOGNIZED_ROLE;
324 }
325
326 return Status::SUCCESS;
327 }
328
329 Status getTypeCPortNamesHelper(std::unordered_map<std::string, bool> *names) {
330 DIR *dp;
331
332 dp = opendir("/sys/class/typec");
333 if (dp != NULL) {
334 int32_t ports = 0;
335 int32_t current = 0;
336 struct dirent *ep;
337
338 while ((ep = readdir(dp))) {
339 if (ep->d_type == DT_LNK) {
340 if (std::string::npos == std::string(ep->d_name).find("-partner")) {
341 std::unordered_map<std::string, bool>::const_iterator portName =
342 names->find(ep->d_name);
343 if (portName == names->end()) {
344 names->insert({ep->d_name, false});
345 }
346 } else {
347 (*names)[std::strtok(ep->d_name, "-")] = true;
348 }
349 }
350 }
351 closedir(dp);
352 return Status::SUCCESS;
353 }
354
355 ALOGE("Failed to open /sys/class/typec");
356 return Status::ERROR;
357 }
358
359 bool canSwitchRoleHelper(const std::string &portName, PortRoleType /*type*/) {
360 std::string filename =
361 "/sys/class/typec/" + portName + "-partner/supports_usb_power_delivery";
362 std::string supportsPD;
363
364 if (!readFile(filename, &supportsPD)) {
365 if (supportsPD == "yes") {
366 return true;
367 }
368 }
369
370 return false;
371 }
372
373 /*
374 * Reuse the same method for both V1_0 and V1_1 callback objects.
375 * The caller of this method would reconstruct the V1_0::PortStatus
376 * object if required.
377 */
378 Status getPortStatusHelper(hidl_vec<PortStatus_1_1> *currentPortStatus_1_1,
379 bool V1_0) {
380 std::unordered_map<std::string, bool> names;
381 Status result = getTypeCPortNamesHelper(&names);
382 int i = -1;
383
384 if (result == Status::SUCCESS) {
385 currentPortStatus_1_1->resize(names.size());
386 for (std::pair<std::string, bool> port : names) {
387 i++;
388 ALOGI("%s", port.first.c_str());
389 (*currentPortStatus_1_1)[i].status.portName = port.first;
390
391 uint32_t currentRole;
392 if (getCurrentRoleHelper(port.first, port.second,
393 PortRoleType::POWER_ROLE,
394 &currentRole) == Status::SUCCESS) {
395 (*currentPortStatus_1_1)[i].status.currentPowerRole =
396 static_cast<PortPowerRole>(currentRole);
397 } else {
398 ALOGE("Error while retreiving portNames");
399 goto done;
400 }
401
402 if (getCurrentRoleHelper(port.first, port.second, PortRoleType::DATA_ROLE,
403 &currentRole) == Status::SUCCESS) {
404 (*currentPortStatus_1_1)[i].status.currentDataRole =
405 static_cast<PortDataRole>(currentRole);
406 } else {
407 ALOGE("Error while retreiving current port role");
408 goto done;
409 }
410
411 if (getCurrentRoleHelper(port.first, port.second, PortRoleType::MODE,
412 &currentRole) == Status::SUCCESS) {
413 (*currentPortStatus_1_1)[i].currentMode =
414 static_cast<PortMode_1_1>(currentRole);
415 (*currentPortStatus_1_1)[i].status.currentMode =
416 static_cast<V1_0::PortMode>(currentRole);
417 } else {
418 ALOGE("Error while retreiving current data role");
419 goto done;
420 }
421
422 (*currentPortStatus_1_1)[i].status.canChangeMode = true;
423 (*currentPortStatus_1_1)[i].status.canChangeDataRole =
424 port.second ? canSwitchRoleHelper(port.first, PortRoleType::DATA_ROLE)
425 : false;
426 (*currentPortStatus_1_1)[i].status.canChangePowerRole =
427 port.second
428 ? canSwitchRoleHelper(port.first, PortRoleType::POWER_ROLE)
429 : false;
430
431 ALOGI("connected:%d canChangeMode:%d canChagedata:%d canChangePower:%d",
432 port.second, (*currentPortStatus_1_1)[i].status.canChangeMode,
433 (*currentPortStatus_1_1)[i].status.canChangeDataRole,
434 (*currentPortStatus_1_1)[i].status.canChangePowerRole);
435
436 if (V1_0) {
437 (*currentPortStatus_1_1)[i].status.supportedModes = V1_0::PortMode::DFP;
438 } else {
439 (*currentPortStatus_1_1)[i].supportedModes = PortMode_1_1::UFP | PortMode_1_1::DFP;
440 (*currentPortStatus_1_1)[i].status.supportedModes = V1_0::PortMode::NONE;
441 (*currentPortStatus_1_1)[i].status.currentMode = V1_0::PortMode::NONE;
442 }
443 }
444 return Status::SUCCESS;
445 }
446 done:
447 return Status::ERROR;
448 }
449
450 Return<void> Usb::queryPortStatus() {
451 hidl_vec<PortStatus_1_1> currentPortStatus_1_1;
452 hidl_vec<V1_0::PortStatus> currentPortStatus;
453 Status status;
454 sp<IUsbCallback> callback_V1_1 = IUsbCallback::castFrom(mCallback_1_0);
455
456 pthread_mutex_lock(&mLock);
457 if (mCallback_1_0 != NULL) {
458 if (callback_V1_1 != NULL) {
459 status = getPortStatusHelper(&currentPortStatus_1_1, false);
460 } else {
461 status = getPortStatusHelper(&currentPortStatus_1_1, true);
462 currentPortStatus.resize(currentPortStatus_1_1.size());
463 for (unsigned long i = 0; i < currentPortStatus_1_1.size(); i++)
464 currentPortStatus[i] = currentPortStatus_1_1[i].status;
465 }
466
467 Return<void> ret;
468
469 if (callback_V1_1 != NULL)
470 ret = callback_V1_1->notifyPortStatusChange_1_1(currentPortStatus_1_1, status);
471 else
472 ret = mCallback_1_0->notifyPortStatusChange(currentPortStatus, status);
473
474 if (!ret.isOk())
475 ALOGE("queryPortStatus_1_1 error %s", ret.description().c_str());
476 } else {
477 ALOGI("Notifying userspace skipped. Callback is NULL");
478 }
479 pthread_mutex_unlock(&mLock);
480
481 return Void();
482 }
483
484 struct data {
485 int uevent_fd;
486 android::hardware::usb::V1_1::implementation::Usb *usb;
487 };
488
489 static void uevent_event(uint32_t /*epevents*/, struct data *payload) {
490 char msg[UEVENT_MSG_LEN + 2];
491 char *cp;
492 int n;
493
494 n = uevent_kernel_multicast_recv(payload->uevent_fd, msg, UEVENT_MSG_LEN);
495 if (n <= 0) return;
496 if (n >= UEVENT_MSG_LEN) /* overflow -- discard */
497 return;
498
499 msg[n] = '\0';
500 msg[n + 1] = '\0';
501 cp = msg;
502
503 while (*cp) {
504 if (std::regex_match(cp, std::regex("(add)(.*)(-partner)"))) {
505 ALOGI("partner added");
506 pthread_mutex_lock(&payload->usb->mPartnerLock);
507 payload->usb->mPartnerUp = true;
508 pthread_cond_signal(&payload->usb->mPartnerCV);
509 pthread_mutex_unlock(&payload->usb->mPartnerLock);
510 } else if (!strncmp(cp, "DEVTYPE=typec_", strlen("DEVTYPE=typec_"))) {
511 hidl_vec<PortStatus_1_1> currentPortStatus_1_1;
512 ALOGI("uevent received %s", cp);
513 pthread_mutex_lock(&payload->usb->mLock);
514 if (payload->usb->mCallback_1_0 != NULL) {
515 sp<IUsbCallback> callback_V1_1 = IUsbCallback::castFrom(payload->usb->mCallback_1_0);
516 Return<void> ret;
517
518 // V1_1 callback
519 if (callback_V1_1 != NULL) {
520 Status status = getPortStatusHelper(&currentPortStatus_1_1, false);
521 ret = callback_V1_1->notifyPortStatusChange_1_1(
522 currentPortStatus_1_1, status);
523 } else { // V1_0 callback
524 Status status = getPortStatusHelper(&currentPortStatus_1_1, true);
525
526 /*
527 * Copying the result from getPortStatusHelper
528 * into V1_0::PortStatus to pass back through
529 * the V1_0 callback object.
530 */
531 hidl_vec<V1_0::PortStatus> currentPortStatus;
532 currentPortStatus.resize(currentPortStatus_1_1.size());
533 for (unsigned long i = 0; i < currentPortStatus_1_1.size(); i++)
534 currentPortStatus[i] = currentPortStatus_1_1[i].status;
535
536 ret = payload->usb->mCallback_1_0->notifyPortStatusChange(
537 currentPortStatus, status);
538 }
539 if (!ret.isOk()) ALOGE("error %s", ret.description().c_str());
540 } else {
541 ALOGI("Notifying userspace skipped. Callback is NULL");
542 }
543 pthread_mutex_unlock(&payload->usb->mLock);
544
545 //Role switch is not in progress and port is in disconnected state
546 if (!pthread_mutex_trylock(&payload->usb->mRoleSwitchLock)) {
547 for (unsigned long i = 0; i < currentPortStatus_1_1.size(); i++) {
548 DIR *dp = opendir(std::string("/sys/class/typec/"
549 + std::string(currentPortStatus_1_1[i].status.portName.c_str())
550 + "-partner").c_str());
551 if (dp == NULL) {
552 //PortRole role = {.role = static_cast<uint32_t>(PortMode::UFP)};
553 switchToDrp(currentPortStatus_1_1[i].status.portName);
554 } else {
555 closedir(dp);
556 }
557 }
558 pthread_mutex_unlock(&payload->usb->mRoleSwitchLock);
559 }
560 break;
561 }
562 /* advance to after the next \0 */
563 while (*cp++) {}
564 }
565 }
566
567 void *work(void *param) {
568 int epoll_fd, uevent_fd;
569 struct epoll_event ev;
570 int nevents = 0;
571 struct data payload;
572
573 ALOGE("creating thread");
574
575 uevent_fd = uevent_open_socket(64 * 1024, true);
576
577 if (uevent_fd < 0) {
578 ALOGE("uevent_init: uevent_open_socket failed\n");
579 return NULL;
580 }
581
582 payload.uevent_fd = uevent_fd;
583 payload.usb = (android::hardware::usb::V1_1::implementation::Usb *)param;
584
585 fcntl(uevent_fd, F_SETFL, O_NONBLOCK);
586
587 ev.events = EPOLLIN;
588 ev.data.ptr = (void *)uevent_event;
589
590 epoll_fd = epoll_create(64);
591 if (epoll_fd == -1) {
592 ALOGE("epoll_create failed; errno=%d", errno);
593 goto error;
594 }
595
596 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, uevent_fd, &ev) == -1) {
597 ALOGE("epoll_ctl failed; errno=%d", errno);
598 goto error;
599 }
600
601 while (!destroyThread) {
602 struct epoll_event events[64];
603
604 nevents = epoll_wait(epoll_fd, events, 64, -1);
605 if (nevents == -1) {
606 if (errno == EINTR) continue;
607 ALOGE("usb epoll_wait failed; errno=%d", errno);
608 break;
609 }
610
611 for (int n = 0; n < nevents; ++n) {
612 if (events[n].data.ptr)
613 (*(void (*)(int, struct data *payload))events[n].data.ptr)(
614 events[n].events, &payload);
615 }
616 }
617
618 ALOGI("exiting worker thread");
619 error:
620 close(uevent_fd);
621
622 if (epoll_fd >= 0) close(epoll_fd);
623
624 return NULL;
625 }
626
627 void sighandler(int sig) {
628 if (sig == SIGUSR1) {
629 destroyThread = true;
630 ALOGI("destroy set");
631 return;
632 }
633 signal(SIGUSR1, sighandler);
634 }
635
636 Return<void> Usb::setCallback(const sp<V1_0::IUsbCallback> &callback) {
637
638 sp<IUsbCallback> callback_V1_1 = IUsbCallback::castFrom(callback);
639
640 if (callback != NULL)
641 if (callback_V1_1 == NULL)
642 ALOGI("Registering 1.0 callback");
643
644 pthread_mutex_lock(&mLock);
645 /*
646 * When both the old callback and new callback values are NULL,
647 * there is no need to spin off the worker thread.
648 * When both the values are not NULL, we would already have a
649 * worker thread running, so updating the callback object would
650 * be suffice.
651 */
652 if ((mCallback_1_0 == NULL && callback == NULL) ||
653 (mCallback_1_0 != NULL && callback != NULL)) {
654 /*
655 * Always store as V1_0 callback object. Type cast to V1_1
656 * when the callback is actually invoked.
657 */
658 mCallback_1_0 = callback;
659 pthread_mutex_unlock(&mLock);
660 return Void();
661 }
662
663 mCallback_1_0 = callback;
664 ALOGI("registering callback");
665
666 // Kill the worker thread if the new callback is NULL.
667 if (mCallback_1_0 == NULL) {
668 pthread_mutex_unlock(&mLock);
669 if (!pthread_kill(mPoll, SIGUSR1)) {
670 pthread_join(mPoll, NULL);
671 ALOGI("pthread destroyed");
672 }
673 return Void();
674 }
675
676 destroyThread = false;
677 signal(SIGUSR1, sighandler);
678
679 /*
680 * Create a background thread if the old callback value is NULL
681 * and being updated with a new value.
682 */
683 if (pthread_create(&mPoll, NULL, work, this)) {
684 ALOGE("pthread creation failed %d", errno);
685 mCallback_1_0 = NULL;
686 }
687
688 pthread_mutex_unlock(&mLock);
689 return Void();
690 }
691
692 } // namespace implementation
693 } // namespace V1_0
694 } // namespace usb
695 } // namespace hardware
696 } // namespace android