/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "android.hardware.usb.aidl-service" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Usb.h" using android::base::GetProperty; using android::base::Trim; namespace aidl { namespace android { namespace hardware { namespace usb { constexpr char kTypecPath[] = "/sys/class/typec/"; constexpr char kDataRoleNode[] = "/data_role"; constexpr char kPowerRoleNode[] = "/power_role"; // Set by the signal handler to destroy the thread volatile bool destroyThread; void queryVersionHelper(android::hardware::usb::Usb *usb, std::vector *currentPortStatus); ScopedAStatus Usb::enableUsbData(const string& in_portName, bool in_enable, int64_t in_transactionId) { std::vector currentPortStatus; pthread_mutex_lock(&mLock); if (mCallback != NULL) { ScopedAStatus ret = mCallback->notifyEnableUsbDataStatus( in_portName, true, in_enable ? Status::SUCCESS : Status::ERROR, in_transactionId); if (!ret.isOk()) ALOGE("notifyEnableUsbDataStatus error %s", ret.getDescription().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); queryVersionHelper(this, ¤tPortStatus); return ScopedAStatus::ok(); } ScopedAStatus Usb::enableUsbDataWhileDocked(const string& in_portName, int64_t in_transactionId) { pthread_mutex_lock(&mLock); if (mCallback != NULL) { ScopedAStatus ret = mCallback->notifyEnableUsbDataWhileDockedStatus( in_portName, Status::NOT_SUPPORTED, in_transactionId); if (!ret.isOk()) ALOGE("notifyEnableUsbDataWhileDockedStatus error %s", ret.getDescription().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); return ScopedAStatus::ok(); } ScopedAStatus Usb::resetUsbPort(const string& in_portName, int64_t in_transactionId) { pthread_mutex_lock(&mLock); if (mCallback != NULL) { ScopedAStatus ret = mCallback->notifyResetUsbPortStatus( in_portName, Status::NOT_SUPPORTED, in_transactionId); if (!ret.isOk()) ALOGE("notifyResetUsbPortStatus error %s", ret.getDescription().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); return ScopedAStatus::ok(); } Status queryMoistureDetectionStatus(std::vector *currentPortStatus) { string enabled, status, path, DetectedPath; for (int i = 0; i < currentPortStatus->size(); i++) { (*currentPortStatus)[i].supportedContaminantProtectionModes .push_back(ContaminantProtectionMode::NONE); (*currentPortStatus)[i].contaminantProtectionStatus = ContaminantProtectionStatus::NONE; (*currentPortStatus)[i].contaminantDetectionStatus = ContaminantDetectionStatus::NOT_SUPPORTED; (*currentPortStatus)[i].supportsEnableContaminantPresenceDetection = false; (*currentPortStatus)[i].supportsEnableContaminantPresenceProtection = false; } return Status::SUCCESS; } string appendRoleNodeHelper(const string &portName, PortRole::Tag tag) { string node(kTypecPath + portName); switch (tag) { case PortRole::dataRole: return node + kDataRoleNode; case PortRole::powerRole: return node + kPowerRoleNode; case PortRole::mode: return node + "/port_type"; default: return ""; } } string convertRoletoString(PortRole role) { if (role.getTag() == PortRole::powerRole) { if (role.get() == PortPowerRole::SOURCE) return "source"; else if (role.get() == PortPowerRole::SINK) return "sink"; } else if (role.getTag() == PortRole::dataRole) { if (role.get() == PortDataRole::HOST) return "host"; if (role.get() == PortDataRole::DEVICE) return "device"; } else if (role.getTag() == PortRole::mode) { if (role.get() == PortMode::UFP) return "sink"; if (role.get() == PortMode::DFP) return "source"; } return "none"; } void extractRole(string *roleName) { std::size_t first, last; first = roleName->find("["); last = roleName->find("]"); if (first != string::npos && last != string::npos) { *roleName = roleName->substr(first + 1, last - first - 1); } } void switchToDrp(const string &portName) { string filename = appendRoleNodeHelper(string(portName.c_str()), PortRole::mode); FILE *fp; if (filename != "") { fp = fopen(filename.c_str(), "w"); if (fp != NULL) { int ret = fputs("dual", fp); fclose(fp); if (ret == EOF) ALOGE("Fatal: Error while switching back to drp"); } else { ALOGE("Fatal: Cannot open file to switch back to drp"); } } else { ALOGE("Fatal: invalid node type"); } } bool switchMode(const string &portName, const PortRole &in_role, struct Usb *usb) { string filename = appendRoleNodeHelper(string(portName.c_str()), in_role.getTag()); string written; FILE *fp; bool roleSwitch = false; if (filename == "") { ALOGE("Fatal: invalid node type"); return false; } fp = fopen(filename.c_str(), "w"); if (fp != NULL) { // Hold the lock here to prevent loosing connected signals // as once the file is written the partner added signal // can arrive anytime. pthread_mutex_lock(&usb->mPartnerLock); usb->mPartnerUp = false; int ret = fputs(convertRoletoString(in_role).c_str(), fp); fclose(fp); if (ret != EOF) { struct timespec to; struct timespec now; wait_again: clock_gettime(CLOCK_MONOTONIC, &now); to.tv_sec = now.tv_sec + PORT_TYPE_TIMEOUT; to.tv_nsec = now.tv_nsec; int err = pthread_cond_timedwait(&usb->mPartnerCV, &usb->mPartnerLock, &to); // There are no uevent signals which implies role swap timed out. if (err == ETIMEDOUT) { ALOGI("uevents wait timedout"); // Validity check. } else if (!usb->mPartnerUp) { goto wait_again; // Role switch succeeded since usb->mPartnerUp is true. } else { roleSwitch = true; } } else { ALOGI("Role switch failed while wrting to file"); } pthread_mutex_unlock(&usb->mPartnerLock); } if (!roleSwitch) switchToDrp(string(portName.c_str())); return roleSwitch; } Usb::Usb() : mLock(PTHREAD_MUTEX_INITIALIZER), mRoleSwitchLock(PTHREAD_MUTEX_INITIALIZER), mPartnerLock(PTHREAD_MUTEX_INITIALIZER), mPartnerUp(false) { pthread_condattr_t attr; if (pthread_condattr_init(&attr)) { ALOGE("pthread_condattr_init failed: %s", strerror(errno)); abort(); } if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) { ALOGE("pthread_condattr_setclock failed: %s", strerror(errno)); abort(); } if (pthread_cond_init(&mPartnerCV, &attr)) { ALOGE("pthread_cond_init failed: %s", strerror(errno)); abort(); } if (pthread_condattr_destroy(&attr)) { ALOGE("pthread_condattr_destroy failed: %s", strerror(errno)); abort(); } } ScopedAStatus Usb::switchRole(const string& in_portName, const PortRole& in_role, int64_t in_transactionId) { string filename = appendRoleNodeHelper(string(in_portName.c_str()), in_role.getTag()); string written; FILE *fp; bool roleSwitch = false; if (filename == "") { ALOGE("Fatal: invalid node type"); return ScopedAStatus::ok(); } pthread_mutex_lock(&mRoleSwitchLock); ALOGI("filename write: %s role:%s", filename.c_str(), convertRoletoString(in_role).c_str()); if (in_role.getTag() == PortRole::mode) { roleSwitch = switchMode(in_portName, in_role, this); } else { fp = fopen(filename.c_str(), "w"); if (fp != NULL) { int ret = fputs(convertRoletoString(in_role).c_str(), fp); fclose(fp); if ((ret != EOF) && ReadFileToString(filename, &written)) { written = Trim(written); extractRole(&written); ALOGI("written: %s", written.c_str()); if (written == convertRoletoString(in_role)) { roleSwitch = true; } else { ALOGE("Role switch failed"); } } else { ALOGE("failed to update the new role"); } } else { ALOGE("fopen failed"); } } pthread_mutex_lock(&mLock); if (mCallback != NULL) { ScopedAStatus ret = mCallback->notifyRoleSwitchStatus( in_portName, in_role, roleSwitch ? Status::SUCCESS : Status::ERROR, in_transactionId); if (!ret.isOk()) ALOGE("RoleSwitchStatus error %s", ret.getDescription().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); pthread_mutex_unlock(&mRoleSwitchLock); return ScopedAStatus::ok(); } ScopedAStatus Usb::limitPowerTransfer(const string& in_portName, bool /*in_limit*/, int64_t in_transactionId) { std::vector currentPortStatus; pthread_mutex_lock(&mLock); if (mCallback != NULL && in_transactionId >= 0) { ScopedAStatus ret = mCallback->notifyLimitPowerTransferStatus( in_portName, false, Status::NOT_SUPPORTED, in_transactionId); if (!ret.isOk()) ALOGE("limitPowerTransfer error %s", ret.getDescription().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); return ScopedAStatus::ok(); } Status getAccessoryConnected(const string &portName, string *accessory) { string filename = kTypecPath + portName + "-partner/accessory_mode"; if (!ReadFileToString(filename, accessory)) { ALOGE("getAccessoryConnected: Failed to open filesystem node: %s", filename.c_str()); return Status::ERROR; } *accessory = Trim(*accessory); return Status::SUCCESS; } Status getCurrentRoleHelper(const string &portName, bool connected, PortRole *currentRole) { string filename; string roleName; string accessory; // Mode if (currentRole->getTag() == PortRole::powerRole) { filename = kTypecPath + portName + kPowerRoleNode; currentRole->set(PortPowerRole::NONE); } else if (currentRole->getTag() == PortRole::dataRole) { filename = kTypecPath + portName + kDataRoleNode; currentRole->set(PortDataRole::NONE); } else if (currentRole->getTag() == PortRole::mode) { filename = kTypecPath + portName + kDataRoleNode; currentRole->set(PortMode::NONE); } else { return Status::ERROR; } if (!connected) return Status::SUCCESS; if (currentRole->getTag() == PortRole::mode) { if (getAccessoryConnected(portName, &accessory) != Status::SUCCESS) { return Status::ERROR; } if (accessory == "analog_audio") { currentRole->set(PortMode::AUDIO_ACCESSORY); return Status::SUCCESS; } else if (accessory == "debug") { currentRole->set(PortMode::DEBUG_ACCESSORY); return Status::SUCCESS; } } if (!ReadFileToString(filename, &roleName)) { ALOGE("getCurrentRole: Failed to open filesystem node: %s", filename.c_str()); return Status::ERROR; } roleName = Trim(roleName); extractRole(&roleName); if (roleName == "source") { currentRole->set(PortPowerRole::SOURCE); } else if (roleName == "sink") { currentRole->set(PortPowerRole::SINK); } else if (roleName == "host") { if (currentRole->getTag() == PortRole::dataRole) currentRole->set(PortDataRole::HOST); else currentRole->set(PortMode::DFP); } else if (roleName == "device") { if (currentRole->getTag() == PortRole::dataRole) currentRole->set(PortDataRole::DEVICE); else currentRole->set(PortMode::UFP); } else if (roleName != "none") { /* case for none has already been addressed. * so we check if the role isn't none. */ return Status::UNRECOGNIZED_ROLE; } return Status::SUCCESS; } Status getTypeCPortNamesHelper(std::unordered_map *names) { DIR *dp; dp = opendir(kTypecPath); if (dp != NULL) { struct dirent *ep; while ((ep = readdir(dp))) { if (ep->d_type == DT_LNK) { if (string::npos == string(ep->d_name).find("-partner")) { std::unordered_map::const_iterator portName = names->find(ep->d_name); if (portName == names->end()) { names->insert({ep->d_name, false}); } } else { (*names)[std::strtok(ep->d_name, "-")] = true; } } } closedir(dp); return Status::SUCCESS; } ALOGE("Failed to open /sys/class/typec"); return Status::ERROR; } bool canSwitchRoleHelper(const string &portName) { string filename = kTypecPath + portName + "-partner/supports_usb_power_delivery"; string supportsPD; if (ReadFileToString(filename, &supportsPD)) { supportsPD = Trim(supportsPD); if (supportsPD == "yes") { return true; } } return false; } Status getPortStatusHelper(std::vector *currentPortStatus) { std::unordered_map names; Status result = getTypeCPortNamesHelper(&names); int i = -1; if (result == Status::SUCCESS) { currentPortStatus->resize(names.size()); for (std::pair port : names) { i++; ALOGI("%s", port.first.c_str()); (*currentPortStatus)[i].portName = port.first; PortRole currentRole; currentRole.set(PortPowerRole::NONE); if (getCurrentRoleHelper(port.first, port.second, ¤tRole) == Status::SUCCESS){ (*currentPortStatus)[i].currentPowerRole = currentRole.get(); } else { ALOGE("Error while retrieving portNames"); goto done; } currentRole.set(PortDataRole::NONE); if (getCurrentRoleHelper(port.first, port.second, ¤tRole) == Status::SUCCESS) { (*currentPortStatus)[i].currentDataRole = currentRole.get(); } else { ALOGE("Error while retrieving current port role"); goto done; } currentRole.set(PortMode::NONE); if (getCurrentRoleHelper(port.first, port.second, ¤tRole) == Status::SUCCESS) { (*currentPortStatus)[i].currentMode = currentRole.get(); } else { ALOGE("Error while retrieving current data role"); goto done; } (*currentPortStatus)[i].canChangeMode = true; (*currentPortStatus)[i].canChangeDataRole = port.second ? canSwitchRoleHelper(port.first) : false; (*currentPortStatus)[i].canChangePowerRole = port.second ? canSwitchRoleHelper(port.first) : false; (*currentPortStatus)[i].supportedModes.push_back(PortMode::DRP); (*currentPortStatus)[i].usbDataStatus.push_back(UsbDataStatus::ENABLED); ALOGI("%d:%s connected:%d canChangeMode:%d canChagedata:%d canChangePower:%d " "usbDataEnabled:%d", i, port.first.c_str(), port.second, (*currentPortStatus)[i].canChangeMode, (*currentPortStatus)[i].canChangeDataRole, (*currentPortStatus)[i].canChangePowerRole, 0); } return Status::SUCCESS; } done: return Status::ERROR; } void queryVersionHelper(android::hardware::usb::Usb *usb, std::vector *currentPortStatus) { Status status; pthread_mutex_lock(&usb->mLock); status = getPortStatusHelper(currentPortStatus); queryMoistureDetectionStatus(currentPortStatus); if (usb->mCallback != NULL) { ScopedAStatus ret = usb->mCallback->notifyPortStatusChange(*currentPortStatus, status); if (!ret.isOk()) ALOGE("queryPortStatus error %s", ret.getDescription().c_str()); } else { ALOGI("Notifying userspace skipped. Callback is NULL"); } pthread_mutex_unlock(&usb->mLock); } ScopedAStatus Usb::queryPortStatus(int64_t in_transactionId) { std::vector currentPortStatus; queryVersionHelper(this, ¤tPortStatus); pthread_mutex_lock(&mLock); if (mCallback != NULL) { ScopedAStatus ret = mCallback->notifyQueryPortStatus( "all", Status::SUCCESS, in_transactionId); if (!ret.isOk()) ALOGE("notifyQueryPortStatus error %s", ret.getDescription().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); return ScopedAStatus::ok(); } ScopedAStatus Usb::enableContaminantPresenceDetection(const string& in_portName, bool /*in_enable*/, int64_t in_transactionId) { std::vector currentPortStatus; pthread_mutex_lock(&mLock); if (mCallback != NULL) { ScopedAStatus ret = mCallback->notifyContaminantEnabledStatus( in_portName, false, Status::ERROR, in_transactionId); if (!ret.isOk()) ALOGE("enableContaminantPresenceDetection error %s", ret.getDescription().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); queryVersionHelper(this, ¤tPortStatus); return ScopedAStatus::ok(); } struct data { int uevent_fd; ::aidl::android::hardware::usb::Usb *usb; }; static void uevent_event(uint32_t /*epevents*/, struct data *payload) { char msg[UEVENT_MSG_LEN + 2]; char *cp; int n; n = uevent_kernel_multicast_recv(payload->uevent_fd, msg, UEVENT_MSG_LEN); if (n <= 0) return; if (n >= UEVENT_MSG_LEN) /* overflow -- discard */ return; msg[n] = '\0'; msg[n + 1] = '\0'; cp = msg; while (*cp) { if (std::regex_match(cp, std::regex("(add)(.*)(-partner)"))) { ALOGI("partner added"); pthread_mutex_lock(&payload->usb->mPartnerLock); payload->usb->mPartnerUp = true; pthread_cond_signal(&payload->usb->mPartnerCV); pthread_mutex_unlock(&payload->usb->mPartnerLock); } else if (!strncmp(cp, "DEVTYPE=typec_", strlen("DEVTYPE=typec_"))) { std::vector currentPortStatus; queryVersionHelper(payload->usb, ¤tPortStatus); // Role switch is not in progress and port is in disconnected state if (!pthread_mutex_trylock(&payload->usb->mRoleSwitchLock)) { for (unsigned long i = 0; i < currentPortStatus.size(); i++) { DIR *dp = opendir(string(kTypecPath + string(currentPortStatus[i].portName.c_str()) + "-partner").c_str()); if (dp == NULL) { switchToDrp(currentPortStatus[i].portName); } else { closedir(dp); } } pthread_mutex_unlock(&payload->usb->mRoleSwitchLock); } break; } /* advance to after the next \0 */ while (*cp++) { } } } void *work(void *param) { int epoll_fd, uevent_fd; struct epoll_event ev; int nevents = 0; struct data payload; uevent_fd = uevent_open_socket(UEVENT_MAX_EVENTS * UEVENT_MSG_LEN, true); if (uevent_fd < 0) { ALOGE("uevent_init: uevent_open_socket failed\n"); return NULL; } payload.uevent_fd = uevent_fd; payload.usb = (::aidl::android::hardware::usb::Usb *)param; fcntl(uevent_fd, F_SETFL, O_NONBLOCK); ev.events = EPOLLIN; ev.data.ptr = (void *)uevent_event; epoll_fd = epoll_create(UEVENT_MAX_EVENTS); if (epoll_fd == -1) { ALOGE("epoll_create failed; errno=%d", errno); goto error; } if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, uevent_fd, &ev) == -1) { ALOGE("epoll_ctl failed; errno=%d", errno); goto error; } while (!destroyThread) { struct epoll_event events[UEVENT_MAX_EVENTS]; nevents = epoll_wait(epoll_fd, events, UEVENT_MAX_EVENTS, -1); if (nevents == -1) { if (errno == EINTR) continue; ALOGE("usb epoll_wait failed; errno=%d", errno); break; } for (int n = 0; n < nevents; ++n) { if (events[n].data.ptr) (*(void (*)(int, struct data *payload))events[n].data.ptr)(events[n].events, &payload); } } ALOGI("exiting worker thread"); error: close(uevent_fd); if (epoll_fd >= 0) close(epoll_fd); return NULL; } void sighandler(int sig) { if (sig == SIGUSR1) { destroyThread = true; ALOGI("destroy set"); return; } signal(SIGUSR1, sighandler); } ScopedAStatus Usb::setCallback( const shared_ptr& in_callback) { pthread_mutex_lock(&mLock); if ((mCallback == NULL && in_callback == NULL) || (mCallback != NULL && in_callback != NULL)) { mCallback = in_callback; pthread_mutex_unlock(&mLock); return ScopedAStatus::ok(); } mCallback = in_callback; ALOGI("registering callback"); if (mCallback == NULL) { if (!pthread_kill(mPoll, SIGUSR1)) { pthread_join(mPoll, NULL); ALOGI("pthread destroyed"); } pthread_mutex_unlock(&mLock); return ScopedAStatus::ok(); } destroyThread = false; signal(SIGUSR1, sighandler); /* * Create a background thread if the old callback value is NULL * and being updated with a new value. */ if (pthread_create(&mPoll, NULL, work, this)) { ALOGE("pthread creation failed %d", errno); mCallback = NULL; } pthread_mutex_unlock(&mLock); return ScopedAStatus::ok(); } } // namespace usb } // namespace hardware } // namespace android } // aidl