| /* |
| * 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 "libPixelUsbOverheat" |
| |
| #include "include/pixelusb/UsbOverheatEvent.h" |
| |
| #include <time.h> |
| |
| namespace android { |
| namespace hardware { |
| namespace google { |
| namespace pixel { |
| namespace usb { |
| |
| // Start monitoring the temperature |
| static volatile bool monitorTemperature; |
| |
| constexpr int kEpollEvents = 10; |
| constexpr char kOverheatLock[] = "overheat"; |
| constexpr char kWakeLockPath[] = "/sys/power/wake_lock"; |
| constexpr char kWakeUnlockPath[] = "/sys/power/wake_unlock"; |
| |
| int addEpollFdWakeUp(const unique_fd &epfd, const unique_fd &fd) { |
| struct epoll_event event; |
| int ret; |
| |
| event.data.fd = fd; |
| event.events = EPOLLIN | EPOLLWAKEUP; |
| |
| ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); |
| if (ret) |
| ALOGE("epoll_ctl error %d", errno); |
| |
| return ret; |
| } |
| |
| UsbOverheatEvent::UsbOverheatEvent(const ZoneInfo &monitored_zone, |
| const std::vector<ZoneInfo> &queried_zones, |
| const int &monitor_interval_sec) |
| : monitored_zone_(monitored_zone), |
| queried_zones_(queried_zones), |
| monitor_interval_sec_(monitor_interval_sec), |
| monitor_() { |
| int fd = timerfd_create(CLOCK_BOOTTIME_ALARM, 0); |
| if (fd < 0) { |
| ALOGE("timerfd_create failed: %d", errno); |
| } |
| |
| unique_fd timerFd(timerfd_create(CLOCK_BOOTTIME_ALARM, 0)); |
| if (timerFd == -1) { |
| ALOGE("timerFd failed to create %d", errno); |
| abort(); |
| } |
| |
| unique_fd epollFd(epoll_create(2)); |
| if (epollFd == -1) { |
| ALOGE("epoll_fd_ failed to create %d", errno); |
| abort(); |
| } |
| |
| unique_fd eventFd(eventfd(0, 0)); |
| if (eventFd == -1) { |
| ALOGE("event_fd_ failed to create %d", errno); |
| abort(); |
| } |
| |
| if (addEpollFdWakeUp(epollFd, timerFd) == -1) { |
| ALOGE("Adding timerFd failed"); |
| abort(); |
| } |
| |
| if (addEpollFdWakeUp(epollFd, eventFd) == -1) { |
| ALOGE("Adding eventFd failed"); |
| abort(); |
| } |
| |
| epoll_fd_ = std::move(epollFd); |
| timer_fd_ = std::move(timerFd); |
| event_fd_ = std::move(eventFd); |
| |
| monitor_ = unique_ptr<thread>(new thread(this->monitorThread, this)); |
| registerListener(); |
| } |
| |
| static int wakelock_cnt = 0; |
| static std::mutex wakelock_lock; |
| |
| static void wakeLockAcquire() { |
| lock_guard<mutex> lock(wakelock_lock); |
| |
| wakelock_cnt++; |
| if (wakelock_cnt == 1) { |
| ALOGV("Acquire wakelock"); |
| if (!WriteStringToFile(kOverheatLock, kWakeLockPath)) { |
| ALOGE("Failed to acquire wake lock string"); |
| } |
| } |
| } |
| |
| static void wakeLockRelease() { |
| lock_guard<mutex> lock(wakelock_lock); |
| |
| wakelock_cnt--; |
| if (wakelock_cnt == 0) { |
| ALOGV("Release wakelock"); |
| if (!WriteStringToFile(kOverheatLock, kWakeUnlockPath)) { |
| ALOGE("Failed to acquire wake lock string"); |
| } |
| } |
| } |
| |
| void *UsbOverheatEvent::monitorThread(void *param) { |
| UsbOverheatEvent *overheatEvent = (UsbOverheatEvent *)param; |
| struct epoll_event events[kEpollEvents]; |
| struct itimerspec delay = itimerspec(); |
| |
| while (true) { |
| uint64_t fired; |
| float temperature = 0; |
| string status; |
| |
| for (vector<ZoneInfo>::size_type i = 0; i < overheatEvent->queried_zones_.size(); i++) { |
| if (overheatEvent->getCurrentTemperature(overheatEvent->queried_zones_[i].name_, |
| &temperature)) { |
| if (i == 0) |
| overheatEvent->max_overheat_temp_ = |
| max(temperature, overheatEvent->max_overheat_temp_); |
| status.append(overheatEvent->queried_zones_[i].name_); |
| status.append(":"); |
| status.append(std::to_string(temperature)); |
| status.append(" "); |
| } |
| } |
| ALOGW("%s", status.c_str()); |
| |
| delay.it_value.tv_sec = monitorTemperature ? overheatEvent->monitor_interval_sec_ : 0; |
| int ret = timerfd_settime(overheatEvent->timer_fd_, 0, &delay, NULL); |
| if (ret < 0) { |
| ALOGE("timerfd_settime failed. err:%d tv_sec:%ld", errno, delay.it_value.tv_sec); |
| continue; |
| } |
| |
| wakeLockRelease(); |
| int nrEvents = epoll_wait(overheatEvent->epoll_fd_, events, kEpollEvents, -1); |
| wakeLockAcquire(); |
| if (nrEvents <= 0) { |
| ALOGE("nrEvents negative skipping"); |
| continue; |
| } |
| |
| for (int i = 0; i < nrEvents; i++) { |
| ALOGV("event=%u on fd=%d\n", events[i].events, events[i].data.fd); |
| |
| if (events[i].data.fd == overheatEvent->timer_fd_) { |
| ALOGI("Wake up caused by timer fd"); |
| int numRead = read(overheatEvent->timer_fd_, &fired, sizeof(fired)); |
| if (numRead != sizeof(fired)) { |
| ALOGV("numRead incorrect"); |
| } |
| if (fired != 1) { |
| ALOGV("Fired not set to 1"); |
| } |
| } else { |
| ALOGI("Wake up caused by event fd"); |
| read(overheatEvent->event_fd_, &fired, sizeof(fired)); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| bool UsbOverheatEvent::registerListener() { |
| ALOGV("UsbOverheatEvent::registerListener"); |
| sp<IServiceManager> sm = IServiceManager::getService(); |
| if (sm == NULL) { |
| ALOGE("Hardware service manager is not running"); |
| return false; |
| } |
| Return<bool> result = sm->registerForNotifications(IThermal::descriptor, "", this); |
| if (result.isOk()) { |
| return true; |
| } |
| ALOGE("Failed to register for hardware service manager notifications: %s", |
| result.description().c_str()); |
| return false; |
| } |
| |
| void UsbOverheatEvent::wakeupMonitor() { |
| // <flag> value does not have any significance here |
| uint64_t flag = 100; |
| |
| unsigned long ret = TEMP_FAILURE_RETRY(write(event_fd_, &flag, sizeof(flag))); |
| if (ret < 0) { |
| ALOGE("Error writing eventfd errno=%d", errno); |
| } |
| } |
| |
| bool UsbOverheatEvent::startRecording() { |
| ALOGI("Start recording. monitorTemperature:%d", monitorTemperature ? 1 : 0); |
| // Bail out if temperature was being monitored previously |
| if (monitorTemperature) |
| return true; |
| |
| wakeLockAcquire(); |
| monitorTemperature = true; |
| wakeupMonitor(); |
| return true; |
| } |
| |
| bool UsbOverheatEvent::stopRecording() { |
| ALOGI("Stop recording. monitorTemperature:%d", monitorTemperature ? 1 : 0); |
| // Bail out if temperature was not being monitored previously |
| if (!monitorTemperature) |
| return true; |
| |
| wakeLockRelease(); |
| monitorTemperature = false; |
| wakeupMonitor(); |
| |
| return true; |
| } |
| |
| bool UsbOverheatEvent::getCurrentTemperature(const string &name, float *temp) { |
| ThermalStatus thermal_status; |
| hidl_vec<Temperature> thermal_temperatures; |
| |
| if (thermal_service_ == NULL) |
| return false; |
| |
| auto ret = thermal_service_->getCurrentTemperatures( |
| false, TemperatureType::USB_PORT, |
| [&](ThermalStatus status, hidl_vec<Temperature> temperatures) { |
| thermal_status = status; |
| thermal_temperatures = temperatures; |
| }); |
| |
| if (ret.isOk() && thermal_status.code == ThermalStatusCode::SUCCESS) { |
| for (auto temperature : thermal_temperatures) { |
| if (temperature.name == name) { |
| *temp = temperature.value; |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| float UsbOverheatEvent::getMaxOverheatTemperature() { |
| return max_overheat_temp_; |
| } |
| |
| Return<void> UsbOverheatEvent::onRegistration(const hidl_string & /*fully_qualified_name*/, |
| const hidl_string & /*instance_name*/, |
| bool /*pre_existing*/) { |
| ThermalStatus thermal_status; |
| |
| thermal_service_ = IThermal::getService(); |
| if (thermal_service_ == NULL) { |
| ALOGE("Unable to get Themal Service"); |
| return Void(); |
| } |
| |
| auto ret = thermal_service_->registerThermalChangedCallback( |
| this, true, monitored_zone_.type_, |
| [&](ThermalStatus status) { thermal_status = status; }); |
| |
| if (!ret.isOk() || thermal_status.code != ThermalStatusCode::SUCCESS) { |
| ALOGE("failed to register thermal changed callback!"); |
| } |
| |
| return Void(); |
| } |
| |
| Return<void> UsbOverheatEvent::notifyThrottling(const Temperature &temperature) { |
| ALOGV("notifyThrottling '%s' T=%2.2f throttlingStatus=%d", temperature.name.c_str(), |
| temperature.value, temperature.throttlingStatus); |
| if (temperature.type == monitored_zone_.type_) { |
| if (temperature.throttlingStatus >= monitored_zone_.severity_) { |
| startRecording(); |
| } else { |
| stopRecording(); |
| } |
| } |
| return Void(); |
| } |
| |
| ZoneInfo::ZoneInfo(const TemperatureType &type, const string &name, |
| const ThrottlingSeverity &severity) |
| : type_(type), name_(name), severity_(severity) {} |
| } // namespace usb |
| } // namespace pixel |
| } // namespace google |
| } // namespace hardware |
| } // namespace android |