/* * Copyright (C) 2017 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@1.2-service.sunfish" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Usb.h" using android::base::GetProperty; namespace android { namespace hardware { namespace usb { namespace V1_2 { namespace implementation { // Set by the signal handler to destroy the thread volatile bool destroyThread; constexpr char kEnabledPath[] = "/sys/class/power_supply/usb/moisture_detection_enabled"; constexpr char kDetectedPath[] = "/sys/class/power_supply/usb/moisture_detected"; constexpr char kConsole[] = "init.svc.console"; constexpr char kDisableContatminantDetection[] = "vendor.usb.contaminantdisable"; void queryVersionHelper(android::hardware::usb::V1_2::implementation::Usb *usb, hidl_vec *currentPortStatus_1_2); int32_t readFile(const std::string &filename, std::string *contents) { FILE *fp; ssize_t read = 0; char *line = NULL; size_t len = 0; fp = fopen(filename.c_str(), "r"); if (fp != NULL) { if ((read = getline(&line, &len, fp)) != -1) { char *pos; if ((pos = strchr(line, '\n')) != NULL) *pos = '\0'; *contents = line; } free(line); fclose(fp); return 0; } else { ALOGE("fopen failed"); } return -1; } int32_t writeFile(const std::string &filename, const std::string &contents) { FILE *fp; std::string written; fp = fopen(filename.c_str(), "w"); if (fp != NULL) { // FAILURE RETRY int ret = fputs(contents.c_str(), fp); fclose(fp); if ((ret != EOF) && !readFile(filename, &written) && written == contents) return 0; } return -1; } Status queryMoistureDetectionStatus(hidl_vec *currentPortStatus_1_2) { std::string enabled, status; if (currentPortStatus_1_2 == NULL || currentPortStatus_1_2->size() == 0) { ALOGE("currentPortStatus_1_2 is not available"); return Status::ERROR; } (*currentPortStatus_1_2)[0].supportedContaminantProtectionModes = 0; (*currentPortStatus_1_2)[0].supportedContaminantProtectionModes |= ContaminantProtectionMode::FORCE_SINK; (*currentPortStatus_1_2)[0].contaminantProtectionStatus = ContaminantProtectionStatus::NONE; (*currentPortStatus_1_2)[0].contaminantDetectionStatus = ContaminantDetectionStatus::DISABLED; (*currentPortStatus_1_2)[0].supportsEnableContaminantPresenceDetection = true; (*currentPortStatus_1_2)[0].supportsEnableContaminantPresenceProtection = false; if (readFile(kEnabledPath, &enabled)) { ALOGE("Failed to open moisture_detection_enabled"); return Status::ERROR; } if (enabled == "1") { if (readFile(kDetectedPath, &status)) { ALOGE("Failed to open moisture_detected"); return Status::ERROR; } if (status == "1") { (*currentPortStatus_1_2)[0].contaminantDetectionStatus = ContaminantDetectionStatus::DETECTED; (*currentPortStatus_1_2)[0].contaminantProtectionStatus = ContaminantProtectionStatus::FORCE_SINK; } else (*currentPortStatus_1_2)[0].contaminantDetectionStatus = ContaminantDetectionStatus::NOT_DETECTED; } ALOGI("ContaminantDetectionStatus:%d ContaminantProtectionStatus:%d", (*currentPortStatus_1_2)[0].contaminantDetectionStatus, (*currentPortStatus_1_2)[0].contaminantProtectionStatus); return Status::SUCCESS; } Return Usb::enableContaminantPresenceDetection(const hidl_string & /*portName*/, bool enable) { std::string status = GetProperty(kConsole, ""); std::string disable = GetProperty(kDisableContatminantDetection, ""); if (status != "running" && disable != "true") writeFile(kEnabledPath, enable ? "1" : "0"); hidl_vec currentPortStatus_1_2; queryVersionHelper(this, ¤tPortStatus_1_2); return Void(); } Return Usb::enableContaminantPresenceProtection(const hidl_string & /*portName*/, bool /*enable*/) { hidl_vec currentPortStatus_1_2; queryVersionHelper(this, ¤tPortStatus_1_2); return Void(); } std::string appendRoleNodeHelper(const std::string &portName, PortRoleType type) { std::string node("/sys/class/typec/" + portName); switch (type) { case PortRoleType::DATA_ROLE: return node + "/data_role"; case PortRoleType::POWER_ROLE: return node + "/power_role"; case PortRoleType::MODE: return node + "/port_type"; default: return ""; } } std::string convertRoletoString(PortRole role) { if (role.type == PortRoleType::POWER_ROLE) { if (role.role == static_cast(PortPowerRole::SOURCE)) return "source"; else if (role.role == static_cast(PortPowerRole::SINK)) return "sink"; } else if (role.type == PortRoleType::DATA_ROLE) { if (role.role == static_cast(PortDataRole::HOST)) return "host"; if (role.role == static_cast(PortDataRole::DEVICE)) return "device"; } else if (role.type == PortRoleType::MODE) { if (role.role == static_cast(PortMode_1_1::UFP)) return "sink"; if (role.role == static_cast(PortMode_1_1::DFP)) return "source"; } return "none"; } void extractRole(std::string *roleName) { std::size_t first, last; first = roleName->find("["); last = roleName->find("]"); if (first != std::string::npos && last != std::string::npos) { *roleName = roleName->substr(first + 1, last - first - 1); } } void switchToDrp(const std::string &portName) { std::string filename = appendRoleNodeHelper(std::string(portName.c_str()), PortRoleType::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 hidl_string &portName, const PortRole &newRole, struct Usb *usb) { std::string filename = appendRoleNodeHelper(std::string(portName.c_str()), newRole.type); std::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(newRole).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"); // Sanity 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(std::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(); } } Return Usb::switchRole(const hidl_string &portName, const V1_0::PortRole &newRole) { std::string filename = appendRoleNodeHelper(std::string(portName.c_str()), newRole.type); std::string written; FILE *fp; bool roleSwitch = false; if (filename == "") { ALOGE("Fatal: invalid node type"); return Void(); } pthread_mutex_lock(&mRoleSwitchLock); ALOGI("filename write: %s role:%s", filename.c_str(), convertRoletoString(newRole).c_str()); if (newRole.type == PortRoleType::MODE) { roleSwitch = switchMode(portName, newRole, this); } else { fp = fopen(filename.c_str(), "w"); if (fp != NULL) { int ret = fputs(convertRoletoString(newRole).c_str(), fp); fclose(fp); if ((ret != EOF) && !readFile(filename, &written)) { extractRole(&written); ALOGI("written: %s", written.c_str()); if (written == convertRoletoString(newRole)) { 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_1_0 != NULL) { Return ret = mCallback_1_0->notifyRoleSwitchStatus( portName, newRole, roleSwitch ? Status::SUCCESS : Status::ERROR); if (!ret.isOk()) ALOGE("RoleSwitchStatus error %s", ret.description().c_str()); } else { ALOGE("Not notifying the userspace. Callback is not set"); } pthread_mutex_unlock(&mLock); pthread_mutex_unlock(&mRoleSwitchLock); return Void(); } Status getAccessoryConnected(const std::string &portName, std::string *accessory) { std::string filename = "/sys/class/typec/" + portName + "-partner/accessory_mode"; if (readFile(filename, accessory)) { ALOGE("getAccessoryConnected: Failed to open filesystem node: %s", filename.c_str()); return Status::ERROR; } return Status::SUCCESS; } Status getCurrentRoleHelper(const std::string &portName, bool connected, PortRoleType type, uint32_t *currentRole) { std::string filename; std::string roleName; std::string accessory; // Mode if (type == PortRoleType::POWER_ROLE) { filename = "/sys/class/typec/" + portName + "/power_role"; *currentRole = static_cast(PortPowerRole::NONE); } else if (type == PortRoleType::DATA_ROLE) { filename = "/sys/class/typec/" + portName + "/data_role"; *currentRole = static_cast(PortDataRole::NONE); } else if (type == PortRoleType::MODE) { filename = "/sys/class/typec/" + portName + "/data_role"; *currentRole = static_cast(PortMode_1_1::NONE); } else { return Status::ERROR; } if (!connected) return Status::SUCCESS; if (type == PortRoleType::MODE) { if (getAccessoryConnected(portName, &accessory) != Status::SUCCESS) { return Status::ERROR; } if (accessory == "analog_audio") { *currentRole = static_cast(PortMode_1_1::AUDIO_ACCESSORY); return Status::SUCCESS; } else if (accessory == "debug") { *currentRole = static_cast(PortMode_1_1::DEBUG_ACCESSORY); return Status::SUCCESS; } } if (readFile(filename, &roleName)) { ALOGE("getCurrentRole: Failed to open filesystem node: %s", filename.c_str()); return Status::ERROR; } extractRole(&roleName); if (roleName == "source") { *currentRole = static_cast(PortPowerRole::SOURCE); } else if (roleName == "sink") { *currentRole = static_cast(PortPowerRole::SINK); } else if (roleName == "host") { if (type == PortRoleType::DATA_ROLE) *currentRole = static_cast(PortDataRole::HOST); else *currentRole = static_cast(PortMode_1_1::DFP); } else if (roleName == "device") { if (type == PortRoleType::DATA_ROLE) *currentRole = static_cast(PortDataRole::DEVICE); else *currentRole = static_cast(PortMode_1_1::UFP); } else if (roleName != "none") { /* case for none has already been addressed. * so we check if the role isnt none. */ return Status::UNRECOGNIZED_ROLE; } return Status::SUCCESS; } Status getTypeCPortNamesHelper(std::unordered_map *names) { DIR *dp; dp = opendir("/sys/class/typec"); if (dp != NULL) { struct dirent *ep; while ((ep = readdir(dp))) { if (ep->d_type == DT_LNK) { if (std::string::npos == std::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 std::string &portName, PortRoleType /*type*/) { std::string filename = "/sys/class/typec/" + portName + "-partner/supports_usb_power_delivery"; std::string supportsPD; if (!readFile(filename, &supportsPD)) { if (supportsPD == "yes") { return true; } } return false; } /* * Reuse the same method for both V1_0 and V1_1 callback objects. * The caller of this method would reconstruct the V1_0::PortStatus * object if required. */ Status getPortStatusHelper(hidl_vec *currentPortStatus_1_2, HALVersion version) { std::unordered_map names; Status result = getTypeCPortNamesHelper(&names); int i = -1; if (result == Status::SUCCESS) { currentPortStatus_1_2->resize(names.size()); for (std::pair port : names) { i++; ALOGI("%s", port.first.c_str()); (*currentPortStatus_1_2)[i].status_1_1.status.portName = port.first; uint32_t currentRole; if (getCurrentRoleHelper(port.first, port.second, PortRoleType::POWER_ROLE, ¤tRole) == Status::SUCCESS) { (*currentPortStatus_1_2)[i].status_1_1.status.currentPowerRole = static_cast(currentRole); } else { ALOGE("Error while retrieving portNames"); goto done; } if (getCurrentRoleHelper(port.first, port.second, PortRoleType::DATA_ROLE, ¤tRole) == Status::SUCCESS) { (*currentPortStatus_1_2)[i].status_1_1.status.currentDataRole = static_cast(currentRole); } else { ALOGE("Error while retrieving current port role"); goto done; } if (getCurrentRoleHelper(port.first, port.second, PortRoleType::MODE, ¤tRole) == Status::SUCCESS) { (*currentPortStatus_1_2)[i].status_1_1.currentMode = static_cast(currentRole); (*currentPortStatus_1_2)[i].status_1_1.status.currentMode = static_cast(currentRole); } else { ALOGE("Error while retrieving current data role"); goto done; } (*currentPortStatus_1_2)[i].status_1_1.status.canChangeMode = true; (*currentPortStatus_1_2)[i].status_1_1.status.canChangeDataRole = port.second ? canSwitchRoleHelper(port.first, PortRoleType::DATA_ROLE) : false; (*currentPortStatus_1_2)[i].status_1_1.status.canChangePowerRole = port.second ? canSwitchRoleHelper(port.first, PortRoleType::POWER_ROLE) : false; if (version == HALVersion::V1_0) { ALOGI("HAL version V1_0"); (*currentPortStatus_1_2)[i].status_1_1.status.supportedModes = V1_0::PortMode::DRP; } else { if (version == HALVersion::V1_1) ALOGI("HAL version V1_1"); else ALOGI("HAL version V1_2"); (*currentPortStatus_1_2)[i].status_1_1.supportedModes = 0 | PortMode_1_1::DRP; (*currentPortStatus_1_2)[i].status_1_1.status.supportedModes = V1_0::PortMode::NONE; (*currentPortStatus_1_2)[i].status_1_1.status.currentMode = V1_0::PortMode::NONE; } ALOGI( "%d:%s connected:%d canChangeMode:%d canChagedata:%d canChangePower:%d " "supportedModes:%d", i, port.first.c_str(), port.second, (*currentPortStatus_1_2)[i].status_1_1.status.canChangeMode, (*currentPortStatus_1_2)[i].status_1_1.status.canChangeDataRole, (*currentPortStatus_1_2)[i].status_1_1.status.canChangePowerRole, (*currentPortStatus_1_2)[i].status_1_1.supportedModes); } return Status::SUCCESS; } done: return Status::ERROR; } void queryVersionHelper(android::hardware::usb::V1_2::implementation::Usb *usb, hidl_vec *currentPortStatus_1_2) { hidl_vec currentPortStatus_1_1; hidl_vec currentPortStatus; Status status; sp callback_V1_1 = V1_1::IUsbCallback::castFrom(usb->mCallback_1_0); sp callback_V1_2 = IUsbCallback::castFrom(usb->mCallback_1_0); pthread_mutex_lock(&usb->mLock); if (usb->mCallback_1_0 != NULL) { if (callback_V1_2 != NULL) { status = getPortStatusHelper(currentPortStatus_1_2, HALVersion::V1_2); if (status == Status::SUCCESS) queryMoistureDetectionStatus(currentPortStatus_1_2); } else if (callback_V1_1 != NULL) { status = getPortStatusHelper(currentPortStatus_1_2, HALVersion::V1_1); currentPortStatus_1_1.resize(currentPortStatus_1_2->size()); for (unsigned long i = 0; i < currentPortStatus_1_2->size(); i++) currentPortStatus_1_1[i] = (*currentPortStatus_1_2)[i].status_1_1; } else { status = getPortStatusHelper(currentPortStatus_1_2, HALVersion::V1_0); currentPortStatus.resize(currentPortStatus_1_2->size()); for (unsigned long i = 0; i < currentPortStatus_1_2->size(); i++) currentPortStatus[i] = (*currentPortStatus_1_2)[i].status_1_1.status; } Return ret; if (callback_V1_2 != NULL) ret = callback_V1_2->notifyPortStatusChange_1_2(*currentPortStatus_1_2, status); else if (callback_V1_1 != NULL) ret = callback_V1_1->notifyPortStatusChange_1_1(currentPortStatus_1_1, status); else ret = usb->mCallback_1_0->notifyPortStatusChange(currentPortStatus, status); if (!ret.isOk()) ALOGE("queryPortStatus_1_2 error %s", ret.description().c_str()); } else { ALOGI("Notifying userspace skipped. Callback is NULL"); } pthread_mutex_unlock(&usb->mLock); } Return Usb::queryPortStatus() { hidl_vec currentPortStatus_1_2; queryVersionHelper(this, ¤tPortStatus_1_2); return Void(); } struct data { int uevent_fd; android::hardware::usb::V1_2::implementation::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_")) || !strncmp(cp, "POWER_SUPPLY_MOISTURE_DETECTED", strlen("POWER_SUPPLY_MOISTURE_DETECTED"))) { hidl_vec currentPortStatus_1_2; queryVersionHelper(payload->usb, ¤tPortStatus_1_2); // 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_1_2.size(); i++) { DIR *dp = opendir(std::string("/sys/class/typec/" + std::string(currentPortStatus_1_2[i] .status_1_1.status.portName.c_str()) + "-partner") .c_str()); if (dp == NULL) { // PortRole role = {.role = static_cast(PortMode::UFP)}; switchToDrp(currentPortStatus_1_2[i].status_1_1.status.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; ALOGE("creating thread"); uevent_fd = uevent_open_socket(64 * 1024, true); if (uevent_fd < 0) { ALOGE("uevent_init: uevent_open_socket failed\n"); return NULL; } payload.uevent_fd = uevent_fd; payload.usb = (android::hardware::usb::V1_2::implementation::Usb *)param; fcntl(uevent_fd, F_SETFL, O_NONBLOCK); ev.events = EPOLLIN; ev.data.ptr = (void *)uevent_event; epoll_fd = epoll_create(64); 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[64]; nevents = epoll_wait(epoll_fd, events, 64, -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); } Return Usb::setCallback(const sp &callback) { sp callback_V1_1 = V1_1::IUsbCallback::castFrom(callback); sp callback_V1_2 = IUsbCallback::castFrom(callback); if (callback != NULL) { if (callback_V1_2 != NULL) ALOGI("Registering 1.2 callback"); else if (callback_V1_1 != NULL) ALOGI("Registering 1.1 callback"); } pthread_mutex_lock(&mLock); /* * When both the old callback and new callback values are NULL, * there is no need to spin off the worker thread. * When both the values are not NULL, we would already have a * worker thread running, so updating the callback object would * be suffice. */ if ((mCallback_1_0 == NULL && callback == NULL) || (mCallback_1_0 != NULL && callback != NULL)) { /* * Always store as V1_0 callback object. Type cast to V1_1 * when the callback is actually invoked. */ mCallback_1_0 = callback; pthread_mutex_unlock(&mLock); return Void(); } mCallback_1_0 = callback; ALOGI("registering callback"); // Kill the worker thread if the new callback is NULL. if (mCallback_1_0 == NULL) { pthread_mutex_unlock(&mLock); if (!pthread_kill(mPoll, SIGUSR1)) { pthread_join(mPoll, NULL); ALOGI("pthread destroyed"); } return Void(); } 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_1_0 = NULL; } pthread_mutex_unlock(&mLock); return Void(); } } // namespace implementation } // namespace V1_2 } // namespace usb } // namespace hardware } // namespace android