diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-09 04:02:21 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-09 04:02:21 +0000 |
commit | 3cbb96c5fe8d8ede6f41a1323233e9dbcde9f7e2 (patch) | |
tree | 22a15b5bfa15d5c039d4ebea2b85b92d01ba02bd | |
parent | 09417ff9b66cfbbba59764c59dacc6a218f9fbb7 (diff) | |
parent | 8d10e34fe05fc31b0604bad062424404f91cfbab (diff) | |
download | gs201-3cbb96c5fe8d8ede6f41a1323233e9dbcde9f7e2.tar.gz |
Snap for 11200327 from 8d10e34fe05fc31b0604bad062424404f91cfbab to 24Q1-release
Change-Id: I3ae47805403cdb0f345d77edebc54c7d4c74f67c
-rw-r--r-- | usb/usb/Android.bp | 1 | ||||
-rw-r--r-- | usb/usb/Usb.cpp | 304 | ||||
-rw-r--r-- | usb/usb/Usb.h | 34 | ||||
-rw-r--r-- | usb/usb/UsbDataSessionMonitor.cpp | 420 | ||||
-rw-r--r-- | usb/usb/UsbDataSessionMonitor.h | 114 |
5 files changed, 596 insertions, 277 deletions
diff --git a/usb/usb/Android.bp b/usb/usb/Android.bp index 8fd373b..609af17 100644 --- a/usb/usb/Android.bp +++ b/usb/usb/Android.bp @@ -34,6 +34,7 @@ cc_binary { srcs: [ "service.cpp", "Usb.cpp", + "UsbDataSessionMonitor.cpp", ], shared_libs: [ "libbase", diff --git a/usb/usb/Usb.cpp b/usb/usb/Usb.cpp index 224951a..555e657 100644 --- a/usb/usb/Usb.cpp +++ b/usb/usb/Usb.cpp @@ -29,7 +29,6 @@ #include <sys/types.h> #include <unistd.h> #include <usbhost/usbhost.h> -#include <chrono> #include <regex> #include <thread> #include <unordered_map> @@ -44,7 +43,6 @@ #include <aidl/android/frameworks/stats/IStats.h> #include <android_hardware_usb_flags.h> -#include <pixelusb/CommonUtils.h> #include <pixelusb/UsbGadgetAidlCommon.h> #include <pixelstats/StatsHelper.h> @@ -57,9 +55,6 @@ using android::base::Trim; using android::hardware::google::pixel::getStatsService; using android::hardware::google::pixel::PixelAtoms::VendorUsbPortOverheat; using android::hardware::google::pixel::reportUsbPortOverheat; -using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent; -using android::hardware::google::pixel::reportUsbDataSessionEvent; -using android::hardware::google::pixel::usb::BuildVendorUsbDataSessionEvent; using android::String8; using android::Vector; @@ -95,17 +90,22 @@ constexpr char kThermalZoneForTempReadSecondary2[] = "qi_therm"; constexpr char kPogoUsbActive[] = "/sys/devices/platform/google,pogo/pogo_usb_active"; constexpr char KPogoMoveDataToUsb[] = "/sys/devices/platform/google,pogo/move_data_to_usb"; constexpr char kPowerSupplyUsbType[] = "/sys/class/power_supply/usb/usb_type"; -constexpr char kUdcState[] = "/sys/devices/platform/11210000.usb/11210000.dwc3/udc/11210000.dwc3/state"; -// xhci-hcd-exynos and usb device numbering could vary on different platforms -constexpr char kHostUeventRegex[] = "^(bind|unbind)@(/devices/platform/11210000\\.usb/11210000\\.dwc3/xhci-hcd-exynos\\.[0-9]\\.auto/)usb([0-9])/[0-9]-0:1\\.0"; +constexpr char kUdcUeventRegex[] = + "/devices/platform/11210000.usb/11210000.dwc3/udc/11210000.dwc3"; +constexpr char kUdcStatePath[] = + "/sys/devices/platform/11210000.usb/11210000.dwc3/udc/11210000.dwc3/state"; +constexpr char kHost1UeventRegex[] = + "/devices/platform/11210000.usb/11210000.dwc3/xhci-hcd-exynos.[0-9].auto/usb2/2-0:1.0"; +constexpr char kHost1StatePath[] = "/sys/bus/usb/devices/usb2/2-0:1.0/usb2-port1/state"; +constexpr char kHost2UeventRegex[] = + "/devices/platform/11210000.usb/11210000.dwc3/xhci-hcd-exynos.[0-9].auto/usb3/3-0:1.0"; +constexpr char kHost2StatePath[] = "/sys/bus/usb/devices/usb3/3-0:1.0/usb3-port1/state"; +constexpr char kDataRolePath[] = "/sys/devices/platform/11210000.usb/new_data_role"; constexpr int kSamplingIntervalSec = 5; void queryVersionHelper(android::hardware::usb::Usb *usb, std::vector<PortStatus> *currentPortStatus); -void queryUsbDataSession(android::hardware::usb::Usb *usb, - std::vector<PortStatus> *currentPortStatus); -#define USB_STATE_MAX_LEN 20 #define CTRL_TRANSFER_TIMEOUT_MSEC 1000 #define GL852G_VENDOR_ID 0x05e3 #define GL852G_PRODUCT_ID1 0x0608 @@ -538,11 +538,20 @@ void *usbHostWork(void *param) { return NULL; } +void updatePortStatus(android::hardware::usb::Usb *usb) { + std::vector<PortStatus> currentPortStatus; + + queryVersionHelper(usb, ¤tPortStatus); +} + Usb::Usb() : mLock(PTHREAD_MUTEX_INITIALIZER), mRoleSwitchLock(PTHREAD_MUTEX_INITIALIZER), mPartnerLock(PTHREAD_MUTEX_INITIALIZER), mPartnerUp(false), + mUsbDataSessionMonitor(kUdcUeventRegex, kUdcStatePath, kHost1UeventRegex, kHost1StatePath, + kHost2UeventRegex, kHost2StatePath, kDataRolePath, + std::bind(&updatePortStatus, this)), mOverheat(ZoneInfo(TemperatureType::USB_PORT, kThermalZoneForTrip, ThrottlingSeverity::CRITICAL), {ZoneInfo(TemperatureType::UNKNOWN, kThermalZoneForTempReadPrimary, @@ -922,6 +931,18 @@ done: return Status::ERROR; } +void queryUsbDataSession(android::hardware::usb::Usb *usb, + std::vector<PortStatus> *currentPortStatus) { + std::vector<ComplianceWarning> warnings; + + usb->mUsbDataSessionMonitor.getComplianceWarnings( + (*currentPortStatus)[0].currentDataRole, &warnings); + (*currentPortStatus)[0].complianceWarnings.insert( + (*currentPortStatus)[0].complianceWarnings.end(), + warnings.begin(), + warnings.end()); +} + void queryVersionHelper(android::hardware::usb::Usb *usb, std::vector<PortStatus> *currentPortStatus) { Status status; @@ -1017,194 +1038,17 @@ void report_overheat_event(android::hardware::usb::Usb *usb) { } } -void report_usb_data_session_event(android::hardware::usb::Usb *usb) { - std::vector<VendorUsbDataSessionEvent> events; - - if (usb->mDataRole == PortDataRole::DEVICE) { - VendorUsbDataSessionEvent event; - BuildVendorUsbDataSessionEvent(false /* is_host */, std::chrono::steady_clock::now(), - usb->mDataSessionStart, &usb->mDeviceState.states, - &usb->mDeviceState.timestamps, &event); - events.push_back(event); - } else if (usb->mDataRole == PortDataRole::HOST) { - bool empty = true; - for (auto &entry : usb->mHostStateMap) { - // Host port will at least get an not_attached event after enablement, - // skip upload if no additional state is added. - if (entry.second.states.size() > 1) { - VendorUsbDataSessionEvent event; - BuildVendorUsbDataSessionEvent(true /* is_host */, std::chrono::steady_clock::now(), - usb->mDataSessionStart, &entry.second.states, - &entry.second.timestamps, &event); - events.push_back(event); - empty = false; - } - } - // All host ports have no state update, upload an event to reflect it - if (empty && usb->mHostStateMap.size() > 0) { - VendorUsbDataSessionEvent event; - BuildVendorUsbDataSessionEvent(true /* is_host */, std::chrono::steady_clock::now(), - usb->mDataSessionStart, - &usb->mHostStateMap.begin()->second.states, - &usb->mHostStateMap.begin()->second.timestamps, - &event); - events.push_back(event); - } - } else { - return; - } - - const shared_ptr<IStats> stats_client = getStatsService(); - if (!stats_client) { - ALOGE("Unable to get AIDL Stats service"); - return; - } - - for (auto &event : events) { - reportUsbDataSessionEvent(stats_client, event); - } -} - -static void unregisterEpollEntry(Usb *usb, std::string name) { - std::map<std::string, struct Usb::epollEntry> *map; - int fd; - - map = &usb->mEpollEntries; - auto it = map->find(name); - if (it != map->end()) { - ALOGI("epoll unregister %s", name.c_str()); - fd = it->second.payload.fd; - epoll_ctl(usb->mEpollFd, EPOLL_CTL_DEL, fd, NULL); - close(fd); - map->erase(it); - } -} - -static void unregisterEpollEntries(Usb *usb) { - std::map<std::string, struct Usb::epollEntry> *map; - std::string name; - - map = &usb->mEpollEntries; - for (auto it = map->begin(); it != map->end();) { - name = it->first; - it++; - unregisterEpollEntry(usb, name); - } -} - -static int registerEpollEntry(Usb *usb, std::string name, int fd, int flags, - void (*func)(uint32_t, struct Usb::payload*)) { - std::map<std::string, struct Usb::epollEntry> *map; - struct Usb::epollEntry *entry; - struct epoll_event ev; - - map = &usb->mEpollEntries; - if (map->find(name) != map->end()) { - ALOGE("%s already registered", name.c_str()); - unregisterEpollEntry(usb, name); - } - - entry = &(*map)[name]; - entry->payload.fd = fd; - entry->payload.name = name; - entry->payload.usb = usb; - entry->cb = std::bind(func, std::placeholders::_1, &entry->payload); - - ev.events = flags; - ev.data.ptr = (void *)&entry->cb; - - if (epoll_ctl(usb->mEpollFd, EPOLL_CTL_ADD, fd, &ev) != 0) { - ALOGE("epoll_ctl failed; errno=%d", errno); - unregisterEpollEntry(usb, name); - return -1; - } - - ALOGI("epoll register %s", name.c_str()); - - return 0; -} - -static int registerEpollEntryByFile(Usb *usb, std::string name, int flags, - void (*func)(uint32_t, struct Usb::payload*)) { - int fd; - - fd = open(name.c_str(), O_RDONLY); - if (fd < 0) { - ALOGE("Cannot open %s", name.c_str()); - return -1; - } - - return registerEpollEntry(usb, name, fd, flags, func); -} - -static void clearUsbDeviceState(struct Usb::usbDeviceState *device) { - device->states.clear(); - device->timestamps.clear(); - device->portResetCount = 0; -} - -static void updateUsbDeviceState(struct Usb::usbDeviceState *device, char *state) { - ALOGI("Update USB device state: %s", state); - - device->states.push_back(state); - device->timestamps.push_back(std::chrono::steady_clock::now()); - - if (!std::strcmp(state, "configured\n")) { - device->portResetCount = 0; - } else if (!std::strcmp(state, "default\n")) { - device->portResetCount++; - } -} - -static void host_event(uint32_t /*epevents*/, struct Usb::payload *payload) { - int n; - char state[USB_STATE_MAX_LEN] = {0}; - struct Usb::usbDeviceState *device; - - lseek(payload->fd, 0, SEEK_SET); - n = read(payload->fd, &state, USB_STATE_MAX_LEN); - - updateUsbDeviceState(&payload->usb->mHostStateMap[payload->name], state); -} - -void queryUsbDataSession(android::hardware::usb::Usb *usb, - std::vector<PortStatus> *currentPortStatus) { - PortDataRole newDataRole = (*currentPortStatus)[0].currentDataRole; - PowerBrickStatus newPowerBrickStatus = (*currentPortStatus)[0].powerBrickStatus; - - if (newDataRole != usb->mDataRole) { - // Upload metrics for the last non-powerbrick data session that has ended - if (usb->mDataRole != PortDataRole::NONE && !usb->mIsPowerBrickConnected) { - report_usb_data_session_event(usb); - } - - // Set up for the new data session - usb->mDataRole = newDataRole; - usb->mDataSessionStart = std::chrono::steady_clock::now(); - usb->mIsPowerBrickConnected = (newPowerBrickStatus == PowerBrickStatus::CONNECTED); - if (newDataRole == PortDataRole::DEVICE) { - clearUsbDeviceState(&usb->mDeviceState); - } else if (newDataRole == PortDataRole::HOST) { - for (auto &entry : usb->mHostStateMap) { - clearUsbDeviceState(&entry.second); - } - } - } - - // PowerBrickStatus could flip from DISCONNECTED to CONNECTED during the same data - // session when BC1.2 SDP times out and falls back to DCP - if (newPowerBrickStatus == PowerBrickStatus::CONNECTED) { - usb->mIsPowerBrickConnected = true; - } -} +struct data { + int uevent_fd; + ::aidl::android::hardware::usb::Usb *usb; +}; -static void uevent_event(uint32_t /*epevents*/, struct Usb::payload *payload) { +static void uevent_event(uint32_t /*epevents*/, struct data *payload) { char msg[UEVENT_MSG_LEN + 2]; char *cp; int n; - std::cmatch match; - n = uevent_kernel_multicast_recv(payload->fd, msg, UEVENT_MSG_LEN); + n = uevent_kernel_multicast_recv(payload->uevent_fd, msg, UEVENT_MSG_LEN); if (n <= 0) return; if (n >= UEVENT_MSG_LEN) /* overflow -- discard */ @@ -1250,28 +1094,6 @@ static void uevent_event(uint32_t /*epevents*/, struct Usb::payload *payload) { } else if (!strncmp(cp, kOverheatStatsDev, strlen(kOverheatStatsDev))) { ALOGV("Overheat Cooling device suez update"); report_overheat_event(payload->usb); - } else if (std::regex_match(cp, match, std::regex(kHostUeventRegex))) { - /* - * Matched strings: - * 1st: entire string - * 2nd: uevent action, either "bind" or "unbind" - * 3rd: xhci device path, e.g. devices/platform/11210000.usb/11210000.dwc3/xhci-hcd-exynos.4.auto - * 4th: usb device number, e.g. 1 for usb1 - * - * The strings are used to composed usb device state path, e.g. - * /sys/devices/platform/11210000.usb/11210000.dwc3/xhci-hcd-exynos.4.auto/usb2/2-0:1.0/usb2-port1/state - */ - if (match.size() == 4) { - std::string action = match[1].str(); - std::string id = match[3].str(); - std::string path = "/sys" + match[2].str() + "usb" + id + "/" + - id + "-0:1.0/usb" + id + "-port1/state"; - if (action == "bind") { - registerEpollEntryByFile(payload->usb, path, EPOLLPRI, host_event); - } else if (action == "unbind") { - unregisterEpollEntry(payload->usb, path); - } - } } /* advance to after the next \0 */ while (*cp++) { @@ -1279,46 +1101,37 @@ static void uevent_event(uint32_t /*epevents*/, struct Usb::payload *payload) { } } -static void udc_event(uint32_t /*epevents*/, struct Usb::payload *payload) { - int n; - char state[USB_STATE_MAX_LEN] = {0}; - - lseek(payload->fd, 0, SEEK_SET); - n = read(payload->fd, &state, USB_STATE_MAX_LEN); - - updateUsbDeviceState(&payload->usb->mDeviceState, state); -} - void *work(void *param) { int epoll_fd, uevent_fd; + struct epoll_event ev; int nevents = 0; - Usb *usb = (Usb *)param; + struct data payload; ALOGE("creating thread"); - epoll_fd = epoll_create(64); - if (epoll_fd == -1) { - ALOGE("epoll_create failed; errno=%d", errno); - return NULL; - } - usb->mEpollFd = epoll_fd; - - // Monitor uevent uevent_fd = uevent_open_socket(64 * 1024, true); + if (uevent_fd < 0) { - ALOGE("uevent_init: uevent_open_socket failed"); - goto error; + 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); - if (registerEpollEntry(usb, "uevent", uevent_fd, EPOLLIN, uevent_event)) { - ALOGE("failed to monitor uevent"); + 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; } - // Monitor udc state - if (registerEpollEntryByFile(usb, kUdcState, EPOLLPRI, udc_event)) { - ALOGE("failed to monitor udc state"); + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, uevent_fd, &ev) == -1) { + ALOGE("epoll_ctl failed; errno=%d", errno); goto error; } @@ -1335,15 +1148,14 @@ void *work(void *param) { for (int n = 0; n < nevents; ++n) { if (events[n].data.ptr) - (*(std::function<void(uint32_t)>*)events[n].data.ptr)(events[n].events); + (*(void (*)(int, struct data *payload))events[n].data.ptr)(events[n].events, + &payload); } } ALOGI("exiting worker thread"); error: - unregisterEpollEntries(usb); - - usb->mEpollFd = -1; + close(uevent_fd); if (epoll_fd >= 0) close(epoll_fd); diff --git a/usb/usb/Usb.h b/usb/usb/Usb.h index 83bae88..495a467 100644 --- a/usb/usb/Usb.h +++ b/usb/usb/Usb.h @@ -19,9 +19,9 @@ #include <android-base/file.h> #include <aidl/android/hardware/usb/BnUsb.h> #include <aidl/android/hardware/usb/BnUsbCallback.h> -#include <chrono> #include <pixelusb/UsbOverheatEvent.h> #include <utils/Log.h> +#include <UsbDataSessionMonitor.h> #define UEVENT_MSG_LEN 2048 // The type-c stack waits for 4.5 - 5.5 secs before declaring a port non-pd. @@ -88,6 +88,8 @@ struct Usb : public BnUsb { // Variable to signal partner coming back online after type switch bool mPartnerUp; + // Report usb data session event and data incompliance warnings + UsbDataSessionMonitor mUsbDataSessionMonitor; // Usb Overheat object for push suez event UsbOverheatEvent mOverheat; // Temperature when connected @@ -98,36 +100,6 @@ struct Usb : public BnUsb { int mUsbHubVendorCmdValue; int mUsbHubVendorCmdIndex; - // USB device state monitoring - struct usbDeviceState { - // Usb device state raw strings read from sysfs - std::vector<std::string> states; - // Timestamps of when the usb device states were captured - std::vector<std::chrono::steady_clock::time_point> timestamps; - int portResetCount; - }; - struct usbDeviceState mDeviceState; - // Map host device path name to usbDeviceState - std::map<std::string, struct usbDeviceState> mHostStateMap; - // Cache relevant info for USB data session metrics collection when a session starts, including - // the data role, power brick status and the time when the session starts. - PortDataRole mDataRole; - bool mIsPowerBrickConnected; - std::chrono::steady_clock::time_point mDataSessionStart; - - // File monitoring through epoll - int mEpollFd; - struct payload { - int fd; - std::string name; - Usb *usb; - }; - struct epollEntry { - struct payload payload; - std::function<void(uint32_t)> cb; - }; - std::map<std::string, struct epollEntry> mEpollEntries; - private: pthread_t mPoll; pthread_t mUsbHost; diff --git a/usb/usb/UsbDataSessionMonitor.cpp b/usb/usb/UsbDataSessionMonitor.cpp new file mode 100644 index 0000000..77defb3 --- /dev/null +++ b/usb/usb/UsbDataSessionMonitor.cpp @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2023 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.UsbDataSessionMonitor" + +#include "UsbDataSessionMonitor.h" + +#include <aidl/android/frameworks/stats/IStats.h> +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android_hardware_usb_flags.h> +#include <cutils/uevent.h> +#include <pixelstats/StatsHelper.h> +#include <pixelusb/CommonUtils.h> +#include <sys/epoll.h> +#include <utils/Log.h> + +#include <regex> + +namespace usb_flags = android::hardware::usb::flags; + +using aidl::android::frameworks::stats::IStats; +using android::base::ReadFileToString; +using android::hardware::google::pixel::getStatsService; +using android::hardware::google::pixel::reportUsbDataSessionEvent; +using android::hardware::google::pixel::PixelAtoms::VendorUsbDataSessionEvent; +using android::hardware::google::pixel::usb::addEpollFd; +using android::hardware::google::pixel::usb::BuildVendorUsbDataSessionEvent; + +namespace aidl { +namespace android { +namespace hardware { +namespace usb { + +#define UEVENT_MSG_LEN 2048 +#define USB_STATE_MAX_LEN 20 +#define DATA_ROLE_MAX_LEN 10 + +constexpr char kUdcConfigfsPath[] = "/config/usb_gadget/g1/UDC"; +constexpr char kNotAttachedState[] = "not attached\n"; +constexpr char kAttachedState[] = "attached\n"; +constexpr char kPoweredState[] = "powered\n"; +constexpr char kDefaultState[] = "default\n"; +constexpr char kAddressedState[] = "addressed\n"; +constexpr char kConfiguredState[] = "configured\n"; +constexpr char kSuspendedState[] = "suspended\n"; +const std::set<std::string> kValidStates = {kNotAttachedState, kAttachedState, kPoweredState, + kDefaultState, kAddressedState, kConfiguredState, + kSuspendedState}; + +static int addEpollFile(const int &epollFd, const std::string &filePath, unique_fd &fileFd) { + struct epoll_event ev; + + unique_fd fd(open(filePath.c_str(), O_RDONLY)); + + if (fd.get() == -1) { + ALOGI("Cannot open %s", filePath.c_str()); + return -1; + } + + ev.data.fd = fd.get(); + ev.events = EPOLLPRI; + + if (epoll_ctl(epollFd, EPOLL_CTL_ADD, fd.get(), &ev) != 0) { + ALOGE("epoll_ctl failed; errno=%d", errno); + return -1; + } + + fileFd = std::move(fd); + ALOGI("epoll registered %s", filePath.c_str()); + return 0; +} + +static void removeEpollFile(const int &epollFd, const std::string &filePath, unique_fd &fileFd) { + epoll_ctl(epollFd, EPOLL_CTL_DEL, fileFd.get(), NULL); + fileFd.release(); + + ALOGI("epoll unregistered %s", filePath.c_str()); +} + +UsbDataSessionMonitor::UsbDataSessionMonitor( + const std::string &deviceUeventRegex, const std::string &deviceStatePath, + const std::string &host1UeventRegex, const std::string &host1StatePath, + const std::string &host2UeventRegex, const std::string &host2StatePath, + const std::string &dataRolePath, std::function<void()> updatePortStatusCb) { + struct epoll_event ev; + std::string udc; + + unique_fd epollFd(epoll_create(8)); + if (epollFd.get() == -1) { + ALOGE("epoll_create failed; errno=%d", errno); + abort(); + } + + unique_fd ueventFd(uevent_open_socket(64 * 1024, true)); + if (ueventFd.get() == -1) { + ALOGE("uevent_open_socket failed"); + abort(); + } + fcntl(ueventFd, F_SETFL, O_NONBLOCK); + + if (addEpollFd(epollFd, ueventFd)) + abort(); + + if (addEpollFile(epollFd.get(), dataRolePath, mDataRoleFd) != 0) { + ALOGE("monitor data role failed"); + abort(); + } + + /* + * The device state file could be absent depending on the current data role + * and driver architecture. It's ok for addEpollFile to fail here, the file + * will be monitored later when its presence is detected by uevent. + */ + mDeviceState.filePath = deviceStatePath; + mDeviceState.ueventRegex = deviceUeventRegex; + addEpollFile(epollFd.get(), mDeviceState.filePath, mDeviceState.fd); + + mHost1State.filePath = host1StatePath; + mHost1State.ueventRegex = host1UeventRegex; + addEpollFile(epollFd.get(), mHost1State.filePath, mHost1State.fd); + + mHost2State.filePath = host2StatePath; + mHost2State.ueventRegex = host2UeventRegex; + addEpollFile(epollFd.get(), mHost2State.filePath, mHost2State.fd); + + mEpollFd = std::move(epollFd); + mUeventFd = std::move(ueventFd); + mUpdatePortStatusCb = updatePortStatusCb; + + if (ReadFileToString(kUdcConfigfsPath, &udc) && !udc.empty()) + mUdcBind = true; + else + mUdcBind = false; + + if (pthread_create(&mMonitor, NULL, this->monitorThread, this)) { + ALOGE("pthread creation failed %d", errno); + abort(); + } +} + +UsbDataSessionMonitor::~UsbDataSessionMonitor() {} + +void UsbDataSessionMonitor::reportUsbDataSessionMetrics() { + std::vector<VendorUsbDataSessionEvent> events; + + if (mDataRole == PortDataRole::DEVICE) { + VendorUsbDataSessionEvent event; + BuildVendorUsbDataSessionEvent(false /* is_host */, boot_clock::now(), mDataSessionStart, + &mDeviceState.states, &mDeviceState.timestamps, &event); + events.push_back(event); + } else if (mDataRole == PortDataRole::HOST) { + bool empty = true; + for (auto e : {&mHost1State, &mHost2State}) { + /* + * Host port will at least get an not_attached event after enablement, + * skip upload if no additional state is added. + */ + if (e->states.size() > 1) { + VendorUsbDataSessionEvent event; + BuildVendorUsbDataSessionEvent(true /* is_host */, boot_clock::now(), + mDataSessionStart, &e->states, &e->timestamps, + &event); + events.push_back(event); + empty = false; + } + } + // All host ports have no state update, upload an event to reflect it + if (empty) { + VendorUsbDataSessionEvent event; + BuildVendorUsbDataSessionEvent(true /* is_host */, boot_clock::now(), mDataSessionStart, + &mHost1State.states, &mHost1State.timestamps, &event); + events.push_back(event); + } + } else { + return; + } + + const std::shared_ptr<IStats> stats_client = getStatsService(); + if (!stats_client) { + ALOGE("Unable to get AIDL Stats service"); + return; + } + + for (auto &event : events) { + reportUsbDataSessionEvent(stats_client, event); + } +} + +void UsbDataSessionMonitor::getComplianceWarnings(const PortDataRole &role, + std::vector<ComplianceWarning> *warnings) { + if (!usb_flags::enable_report_usb_data_compliance_warning()) + return; + + if (role != mDataRole || role == PortDataRole::NONE) + return; + + for (auto w : mWarningSet) { + warnings->push_back(w); + } +} + +void UsbDataSessionMonitor::notifyComplianceWarning() { + if (!usb_flags::enable_report_usb_data_compliance_warning()) + return; + + if (mUpdatePortStatusCb) + mUpdatePortStatusCb(); +} + +void UsbDataSessionMonitor::evaluateComplianceWarning() { + std::set<ComplianceWarning> newWarningSet; + + // TODO: add heuristics and update newWarningSet + if (mDataRole == PortDataRole::DEVICE && mUdcBind) { + } else if (mDataRole == PortDataRole::HOST) { + } + + if (newWarningSet != mWarningSet) { + mWarningSet = newWarningSet; + notifyComplianceWarning(); + } +} + +void UsbDataSessionMonitor::clearDeviceStateEvents(struct usbDeviceState *deviceState) { + deviceState->states.clear(); + deviceState->timestamps.clear(); +} + +void UsbDataSessionMonitor::handleDeviceStateEvent(struct usbDeviceState *deviceState) { + int n; + char state[USB_STATE_MAX_LEN] = {0}; + + lseek(deviceState->fd.get(), 0, SEEK_SET); + n = read(deviceState->fd.get(), &state, USB_STATE_MAX_LEN); + + if (kValidStates.find(state) == kValidStates.end()) { + ALOGE("Invalid state %s", state); + return; + } + + ALOGI("Update USB device state: %s", state); + + deviceState->states.push_back(state); + deviceState->timestamps.push_back(boot_clock::now()); + evaluateComplianceWarning(); +} + +void UsbDataSessionMonitor::handleDataRoleEvent() { + int n; + PortDataRole newDataRole; + char role[DATA_ROLE_MAX_LEN] = {0}; + + lseek(mDataRoleFd.get(), 0, SEEK_SET); + n = read(mDataRoleFd.get(), &role, DATA_ROLE_MAX_LEN); + + ALOGI("Update USB data role %s", role); + + if (!std::strcmp(role, "host")) { + newDataRole = PortDataRole::HOST; + } else if (!std::strcmp(role, "device")) { + newDataRole = PortDataRole::DEVICE; + } else { + newDataRole = PortDataRole::NONE; + } + + if (newDataRole != mDataRole) { + // Upload metrics for the last data session that has ended + if (mDataRole == PortDataRole::HOST || (mDataRole == PortDataRole::DEVICE && mUdcBind)) { + reportUsbDataSessionMetrics(); + } + + // Set up for the new data session + mWarningSet.clear(); + mDataRole = newDataRole; + mDataSessionStart = boot_clock::now(); + + if (newDataRole == PortDataRole::DEVICE) { + clearDeviceStateEvents(&mDeviceState); + } else if (newDataRole == PortDataRole::HOST) { + clearDeviceStateEvents(&mHost1State); + clearDeviceStateEvents(&mHost2State); + } + } +} + +void UsbDataSessionMonitor::updateUdcBindStatus(const std::string &devname) { + std::string function; + bool newUdcBind; + + /* + * /sys/class/udc/<udc>/function prints out name of currently running USB gadget driver + * Ref: https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-udc + * Empty name string means the udc device is not bound and gadget is pulldown. + */ + if (!ReadFileToString("/sys" + devname + "/function", &function)) + return; + + if (function == "") + newUdcBind = false; + else + newUdcBind = true; + + if (newUdcBind == mUdcBind) + return; + + if (mDataRole == PortDataRole::DEVICE) { + if (mUdcBind && !newUdcBind) { + /* + * Gadget soft pulldown: report metrics as the end of a data session and + * re-evaluate compliance warnings to clear existing warnings if any. + */ + reportUsbDataSessionMetrics(); + evaluateComplianceWarning(); + + } else if (!mUdcBind && newUdcBind) { + // Gadget soft pullup: reset and start accounting for a new data session. + clearDeviceStateEvents(&mDeviceState); + mDataSessionStart = boot_clock::now(); + } + } + + ALOGI("Udc bind status changes from %b to %b", mUdcBind, newUdcBind); + mUdcBind = newUdcBind; +} + +void UsbDataSessionMonitor::handleUevent() { + char msg[UEVENT_MSG_LEN + 2]; + char *cp; + int n; + + n = uevent_kernel_multicast_recv(mUeventFd.get(), msg, UEVENT_MSG_LEN); + if (n <= 0) + return; + if (n >= UEVENT_MSG_LEN) + return; + + msg[n] = '\0'; + msg[n + 1] = '\0'; + cp = msg; + + while (*cp) { + for (auto e : {&mHost1State, &mHost2State}) { + if (std::regex_search(cp, std::regex(e->ueventRegex))) { + if (!strncmp(cp, "bind@", strlen("bind@"))) { + addEpollFile(mEpollFd.get(), e->filePath, e->fd); + } else if (!strncmp(cp, "unbind@", strlen("unbind@"))) { + removeEpollFile(mEpollFd.get(), e->filePath, e->fd); + } + } + } + + // TODO: support bind@ unbind@ to detect dynamically allocated udc device + if (std::regex_search(cp, std::regex(mDeviceState.ueventRegex))) { + if (!strncmp(cp, "change@", strlen("change@"))) { + char *devname = cp + strlen("change@"); + /* + * Udc device emits a KOBJ_CHANGE event on configfs driver bind and unbind. + * TODO: upstream udc driver emits KOBJ_CHANGE event BEFORE unbind is actually + * executed. Add a short delay to get the correct state while working on a fix + * upstream. + */ + usleep(50000); + updateUdcBindStatus(devname); + } + } + /* advance to after the next \0 */ + while (*cp++) { + } + } +} + +void *UsbDataSessionMonitor::monitorThread(void *param) { + UsbDataSessionMonitor *monitor = (UsbDataSessionMonitor *)param; + struct epoll_event events[64]; + int nevents = 0; + + while (true) { + nevents = epoll_wait(monitor->mEpollFd.get(), 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.fd == monitor->mUeventFd.get()) { + monitor->handleUevent(); + } else if (events[n].data.fd == monitor->mDataRoleFd.get()) { + monitor->handleDataRoleEvent(); + } else if (events[n].data.fd == monitor->mDeviceState.fd.get()) { + monitor->handleDeviceStateEvent(&monitor->mDeviceState); + } else if (events[n].data.fd == monitor->mHost1State.fd.get()) { + monitor->handleDeviceStateEvent(&monitor->mHost1State); + } else if (events[n].data.fd == monitor->mHost2State.fd.get()) { + monitor->handleDeviceStateEvent(&monitor->mHost2State); + } + } + } + return NULL; +} + +} // namespace usb +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/usb/usb/UsbDataSessionMonitor.h b/usb/usb/UsbDataSessionMonitor.h new file mode 100644 index 0000000..596f378 --- /dev/null +++ b/usb/usb/UsbDataSessionMonitor.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 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. + */ + +#pragma once + +#include <aidl/android/hardware/usb/ComplianceWarning.h> +#include <aidl/android/hardware/usb/PortDataRole.h> +#include <android-base/chrono_utils.h> +#include <android-base/unique_fd.h> + +#include <set> +#include <string> +#include <vector> + +namespace aidl { +namespace android { +namespace hardware { +namespace usb { + +using ::aidl::android::hardware::usb::ComplianceWarning; +using ::aidl::android::hardware::usb::PortDataRole; +using ::android::base::boot_clock; +using ::android::base::unique_fd; + +/* + * UsbDataSessionMonitor monitors the usb device state sysfs of 3 different usb devices + * including device mode (udc), host mode high-speed port and host mode super-speed port. It + * reports Suez metrics for each data session and also provides API to query the compliance + * warnings detected in the current usb data session. + */ +class UsbDataSessionMonitor { + public: + /* + * The host mode high-speed port and super-speed port can be assigned to either host1 or + * host2 without affecting functionality. + * + * UeventRegex: name regex of the device that's being monitored. The regex is matched against + * uevent to detect dynamic creation/deletion/change of the device. + * StatePath: usb device state sysfs path of the device, monitored by epoll. + * dataRolePath: path to the usb data role sysfs, monitored by epoll. + * updatePortStatusCb: the callback is invoked when the compliance warings changes. + */ + UsbDataSessionMonitor(const std::string &deviceUeventRegex, const std::string &deviceStatePath, + const std::string &host1UeventRegex, const std::string &host1StatePath, + const std::string &host2UeventRegex, const std::string &host2StatePath, + const std::string &dataRolePath, + std::function<void()> updatePortStatusCb); + ~UsbDataSessionMonitor(); + // Returns the compliance warnings detected in the current data session. + void getComplianceWarnings(const PortDataRole &role, std::vector<ComplianceWarning> *warnings); + + private: + struct usbDeviceState { + unique_fd fd; + std::string filePath; + std::string ueventRegex; + // Usb device states reported by state sysfs + std::vector<std::string> states; + // Timestamps of when the usb device states were captured + std::vector<boot_clock::time_point> timestamps; + }; + + static void *monitorThread(void *param); + void handleUevent(); + void handleDataRoleEvent(); + void handleDeviceStateEvent(struct usbDeviceState *deviceState); + void clearDeviceStateEvents(struct usbDeviceState *deviceState); + void reportUsbDataSessionMetrics(); + void evaluateComplianceWarning(); + void notifyComplianceWarning(); + void updateUdcBindStatus(const std::string &devname); + + pthread_t mMonitor; + unique_fd mEpollFd; + unique_fd mUeventFd; + unique_fd mDataRoleFd; + struct usbDeviceState mDeviceState; + struct usbDeviceState mHost1State; + struct usbDeviceState mHost2State; + std::set<ComplianceWarning> mWarningSet; + // Callback function to notify the caller when there's a change in compliance warnings. + std::function<void()> mUpdatePortStatusCb; + /* + * Cache relevant info for a USB data session when one starts, including + * the data role and the time when the session starts. + */ + PortDataRole mDataRole; + boot_clock::time_point mDataSessionStart; + /* + * In gadget mode: this indicates whether the udc device is bound to the configfs driver, which + * is done by userspace writing the udc device name to /config/usb_gadget/g1/UDC. When unbound, + * the gadget is in soft pulldown state and is expected not to enumerate. During gadget + * function switch, the udc device usually go through unbind and bind. + */ + bool mUdcBind; +}; + +} // namespace usb +} // namespace hardware +} // namespace android +} // namespace aidl |