2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #define LOG_TAG "android.hardware.usb@1.1-service.wahoo"
19 #include <android-base/logging.h>
26 #include <sys/types.h>
29 #include <unordered_map>
31 #include <cutils/uevent.h>
32 #include <sys/epoll.h>
33 #include <utils/Errors.h>
34 #include <utils/StrongPointer.h>
42 namespace implementation
{
44 // Set by the signal handler to destroy the thread
45 volatile bool destroyThread
;
47 int32_t readFile(const std::string
&filename
, std::string
*contents
) {
53 fp
= fopen(filename
.c_str(), "r");
55 if ((read
= getline(&line
, &len
, fp
)) != -1) {
57 if ((pos
= strchr(line
, '\n')) != NULL
) *pos
= '\0';
64 ALOGE("fopen failed");
70 std::string
appendRoleNodeHelper(const std::string
&portName
,
72 std::string
node("/sys/class/typec/" + portName
);
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";
86 std::string
convertRoletoString(PortRole role
) {
87 if (role
.type
== PortRoleType::POWER_ROLE
) {
88 if (role
.role
== static_cast<uint32_t>(PortPowerRole::SOURCE
))
90 else if (role
.role
== static_cast<uint32_t>(PortPowerRole::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
))
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";
103 void extractRole(std::string
*roleName
) {
104 std::size_t first
, last
;
106 first
= roleName
->find("[");
107 last
= roleName
->find("]");
109 if (first
!= std::string::npos
&& last
!= std::string::npos
) {
110 *roleName
= roleName
->substr(first
+ 1, last
- first
- 1);
114 void switchToDrp(const std::string
&portName
) {
115 std::string filename
=
116 appendRoleNodeHelper(std::string(portName
.c_str()), PortRoleType::MODE
);
119 if (filename
!= "") {
120 fp
= fopen(filename
.c_str(), "w");
122 int ret
= fputs("dual", fp
);
125 ALOGE("Fatal: Error while switching back to drp");
127 ALOGE("Fatal: Cannot open file to switch back to drp");
130 ALOGE("Fatal: invalid node type");
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
);
140 bool roleSwitch
= false;
142 if (filename
== "") {
143 ALOGE("Fatal: invalid node type");
147 fp
= fopen(filename
.c_str(), "w");
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
);
162 gettimeofday(&tp
, NULL
);
163 to
.tv_sec
= tp
.tv_sec
+ PORT_TYPE_TIMEOUT
;
164 to
.tv_nsec
= tp
.tv_usec
* 1000;;
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");
171 } else if (!usb
->mPartnerUp
) {
173 // Role switch succeeded since usb->mPartnerUp is true.
178 ALOGI("Role switch failed while wrting to file");
180 pthread_mutex_unlock(&usb
->mPartnerLock
);
184 switchToDrp(std::string(portName
.c_str()));
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
);
197 bool roleSwitch
= false;
199 if (filename
== "") {
200 ALOGE("Fatal: invalid node type");
204 pthread_mutex_lock(&mRoleSwitchLock
);
206 ALOGI("filename write: %s role:%s", filename
.c_str(),
207 convertRoletoString(newRole
).c_str());
209 if (newRole
.type
== PortRoleType::MODE
) {
210 roleSwitch
= switchMode(portName
, newRole
, this);
212 fp
= fopen(filename
.c_str(), "w");
214 int ret
= fputs(convertRoletoString(newRole
).c_str(), fp
);
216 if ((ret
!= EOF
) && !readFile(filename
, &written
)) {
217 extractRole(&written
);
218 ALOGI("written: %s", written
.c_str());
219 if (written
== convertRoletoString(newRole
)) {
222 ALOGE("Role switch failed");
225 ALOGE("failed to update the new role");
228 ALOGE("fopen failed");
232 pthread_mutex_lock(&mLock
);
233 if (mCallback_1_0
!= NULL
) {
235 mCallback_1_0
->notifyRoleSwitchStatus(portName
, newRole
,
236 roleSwitch
? Status::SUCCESS
: Status::ERROR
);
238 ALOGE("RoleSwitchStatus error %s", ret
.description().c_str());
240 ALOGE("Not notifying the userspace. Callback is not set");
242 pthread_mutex_unlock(&mLock
);
243 pthread_mutex_unlock(&mRoleSwitchLock
);
248 Status
getAccessoryConnected(const std::string
&portName
, std::string
*accessory
) {
249 std::string filename
=
250 "/sys/class/typec/" + portName
+ "-partner/accessory_mode";
252 if (readFile(filename
, accessory
)) {
253 ALOGE("getAccessoryConnected: Failed to open filesystem node: %s",
255 return Status::ERROR
;
258 return Status::SUCCESS
;
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
;
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
);
279 return Status::ERROR
;
282 if (!connected
) return Status::SUCCESS
;
284 if (type
== PortRoleType::MODE
) {
285 if (getAccessoryConnected(portName
, &accessory
) != Status::SUCCESS
) {
286 return Status::ERROR
;
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
;
297 if (readFile(filename
, &roleName
)) {
298 ALOGE("getCurrentRole: Failed to open filesystem node: %s",
300 return Status::ERROR
;
303 extractRole(&roleName
);
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
);
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
);
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.
323 return Status::UNRECOGNIZED_ROLE
;
326 return Status::SUCCESS
;
329 Status
getTypeCPortNamesHelper(std::unordered_map
<std::string
, bool> *names
) {
332 dp
= opendir("/sys/class/typec");
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});
347 (*names
)[std::strtok(ep
->d_name
, "-")] = true;
352 return Status::SUCCESS
;
355 ALOGE("Failed to open /sys/class/typec");
356 return Status::ERROR
;
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
;
364 if (!readFile(filename
, &supportsPD
)) {
365 if (supportsPD
== "yes") {
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.
378 Status
getPortStatusHelper(hidl_vec
<PortStatus_1_1
> *currentPortStatus_1_1
,
380 std::unordered_map
<std::string
, bool> names
;
381 Status result
= getTypeCPortNamesHelper(&names
);
384 if (result
== Status::SUCCESS
) {
385 currentPortStatus_1_1
->resize(names
.size());
386 for (std::pair
<std::string
, bool> port
: names
) {
388 ALOGI("%s", port
.first
.c_str());
389 (*currentPortStatus_1_1
)[i
].status
.portName
= port
.first
;
391 uint32_t currentRole
;
392 if (getCurrentRoleHelper(port
.first
, port
.second
,
393 PortRoleType::POWER_ROLE
,
394 ¤tRole
) == Status::SUCCESS
) {
395 (*currentPortStatus_1_1
)[i
].status
.currentPowerRole
=
396 static_cast<PortPowerRole
>(currentRole
);
398 ALOGE("Error while retreiving portNames");
402 if (getCurrentRoleHelper(port
.first
, port
.second
, PortRoleType::DATA_ROLE
,
403 ¤tRole
) == Status::SUCCESS
) {
404 (*currentPortStatus_1_1
)[i
].status
.currentDataRole
=
405 static_cast<PortDataRole
>(currentRole
);
407 ALOGE("Error while retreiving current port role");
411 if (getCurrentRoleHelper(port
.first
, port
.second
, PortRoleType::MODE
,
412 ¤tRole
) == 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
);
418 ALOGE("Error while retreiving current data role");
422 (*currentPortStatus_1_1
)[i
].status
.canChangeMode
= true;
423 (*currentPortStatus_1_1
)[i
].status
.canChangeDataRole
=
424 port
.second
? canSwitchRoleHelper(port
.first
, PortRoleType::DATA_ROLE
)
426 (*currentPortStatus_1_1
)[i
].status
.canChangePowerRole
=
428 ? canSwitchRoleHelper(port
.first
, PortRoleType::POWER_ROLE
)
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
);
437 (*currentPortStatus_1_1
)[i
].status
.supportedModes
= V1_0::PortMode::DFP
;
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
;
444 return Status::SUCCESS
;
447 return Status::ERROR
;
450 Return
<void> Usb::queryPortStatus() {
451 hidl_vec
<PortStatus_1_1
> currentPortStatus_1_1
;
452 hidl_vec
<V1_0::PortStatus
> currentPortStatus
;
454 sp
<IUsbCallback
> callback_V1_1
= IUsbCallback::castFrom(mCallback_1_0
);
456 pthread_mutex_lock(&mLock
);
457 if (mCallback_1_0
!= NULL
) {
458 if (callback_V1_1
!= NULL
) {
459 status
= getPortStatusHelper(¤tPortStatus_1_1
, false);
461 status
= getPortStatusHelper(¤tPortStatus_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
;
469 if (callback_V1_1
!= NULL
)
470 ret
= callback_V1_1
->notifyPortStatusChange_1_1(currentPortStatus_1_1
, status
);
472 ret
= mCallback_1_0
->notifyPortStatusChange(currentPortStatus
, status
);
475 ALOGE("queryPortStatus_1_1 error %s", ret
.description().c_str());
477 ALOGI("Notifying userspace skipped. Callback is NULL");
479 pthread_mutex_unlock(&mLock
);
486 android::hardware::usb::V1_1::implementation::Usb
*usb
;
489 static void uevent_event(uint32_t /*epevents*/, struct data
*payload
) {
490 char msg
[UEVENT_MSG_LEN
+ 2];
494 n
= uevent_kernel_multicast_recv(payload
->uevent_fd
, msg
, UEVENT_MSG_LEN
);
496 if (n
>= UEVENT_MSG_LEN
) /* overflow -- discard */
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
);
519 if (callback_V1_1
!= NULL
) {
520 Status status
= getPortStatusHelper(¤tPortStatus_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(¤tPortStatus_1_1
, true);
527 * Copying the result from getPortStatusHelper
528 * into V1_0::PortStatus to pass back through
529 * the V1_0 callback object.
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
;
536 ret
= payload
->usb
->mCallback_1_0
->notifyPortStatusChange(
537 currentPortStatus
, status
);
539 if (!ret
.isOk()) ALOGE("error %s", ret
.description().c_str());
541 ALOGI("Notifying userspace skipped. Callback is NULL");
543 pthread_mutex_unlock(&payload
->usb
->mLock
);
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());
552 //PortRole role = {.role = static_cast<uint32_t>(PortMode::UFP)};
553 switchToDrp(currentPortStatus_1_1
[i
].status
.portName
);
558 pthread_mutex_unlock(&payload
->usb
->mRoleSwitchLock
);
562 /* advance to after the next \0 */
567 void *work(void *param
) {
568 int epoll_fd
, uevent_fd
;
569 struct epoll_event ev
;
573 ALOGE("creating thread");
575 uevent_fd
= uevent_open_socket(64 * 1024, true);
578 ALOGE("uevent_init: uevent_open_socket failed\n");
582 payload
.uevent_fd
= uevent_fd
;
583 payload
.usb
= (android::hardware::usb::V1_1::implementation::Usb
*)param
;
585 fcntl(uevent_fd
, F_SETFL
, O_NONBLOCK
);
588 ev
.data
.ptr
= (void *)uevent_event
;
590 epoll_fd
= epoll_create(64);
591 if (epoll_fd
== -1) {
592 ALOGE("epoll_create failed; errno=%d", errno
);
596 if (epoll_ctl(epoll_fd
, EPOLL_CTL_ADD
, uevent_fd
, &ev
) == -1) {
597 ALOGE("epoll_ctl failed; errno=%d", errno
);
601 while (!destroyThread
) {
602 struct epoll_event events
[64];
604 nevents
= epoll_wait(epoll_fd
, events
, 64, -1);
606 if (errno
== EINTR
) continue;
607 ALOGE("usb epoll_wait failed; errno=%d", errno
);
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
);
618 ALOGI("exiting worker thread");
622 if (epoll_fd
>= 0) close(epoll_fd
);
627 void sighandler(int sig
) {
628 if (sig
== SIGUSR1
) {
629 destroyThread
= true;
630 ALOGI("destroy set");
633 signal(SIGUSR1
, sighandler
);
636 Return
<void> Usb::setCallback(const sp
<V1_0::IUsbCallback
> &callback
) {
638 sp
<IUsbCallback
> callback_V1_1
= IUsbCallback::castFrom(callback
);
640 if (callback
!= NULL
)
641 if (callback_V1_1
== NULL
)
642 ALOGI("Registering 1.0 callback");
644 pthread_mutex_lock(&mLock
);
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
652 if ((mCallback_1_0
== NULL
&& callback
== NULL
) ||
653 (mCallback_1_0
!= NULL
&& callback
!= NULL
)) {
655 * Always store as V1_0 callback object. Type cast to V1_1
656 * when the callback is actually invoked.
658 mCallback_1_0
= callback
;
659 pthread_mutex_unlock(&mLock
);
663 mCallback_1_0
= callback
;
664 ALOGI("registering callback");
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");
676 destroyThread
= false;
677 signal(SIGUSR1
, sighandler
);
680 * Create a background thread if the old callback value is NULL
681 * and being updated with a new value.
683 if (pthread_create(&mPoll
, NULL
, work
, this)) {
684 ALOGE("pthread creation failed %d", errno
);
685 mCallback_1_0
= NULL
;
688 pthread_mutex_unlock(&mLock
);
692 } // namespace implementation
695 } // namespace hardware
696 } // namespace android