| /****************************************************************************** |
| * |
| * Copyright (C) 2015 Google, Inc. |
| * |
| * 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 "bt_osi_wakelock" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <hardware/bluetooth.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <pthread.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <string> |
| |
| #include "osi/include/alarm.h" |
| #include "osi/include/allocator.h" |
| #include "osi/include/log.h" |
| #include "osi/include/metrics.h" |
| #include "osi/include/osi.h" |
| #include "osi/include/thread.h" |
| #include "osi/include/wakelock.h" |
| |
| static bt_os_callouts_t *wakelock_os_callouts = NULL; |
| static bool is_native = true; |
| |
| static const clockid_t CLOCK_ID = CLOCK_BOOTTIME; |
| static const char *WAKE_LOCK_ID = "bluetooth_timer"; |
| static const std::string DEFAULT_WAKE_LOCK_PATH = "/sys/power/wake_lock"; |
| static const std::string DEFAULT_WAKE_UNLOCK_PATH = "/sys/power/wake_unlock"; |
| static std::string wake_lock_path; |
| static std::string wake_unlock_path; |
| static ssize_t locked_id_len = -1; |
| static pthread_once_t initialized = PTHREAD_ONCE_INIT; |
| static int wake_lock_fd = INVALID_FD; |
| static int wake_unlock_fd = INVALID_FD; |
| |
| // Wakelock statistics for the "bluetooth_timer" |
| typedef struct { |
| bool is_acquired; |
| size_t acquired_count; |
| size_t released_count; |
| size_t acquired_errors; |
| size_t released_errors; |
| period_ms_t min_acquired_interval_ms; |
| period_ms_t max_acquired_interval_ms; |
| period_ms_t last_acquired_interval_ms; |
| period_ms_t total_acquired_interval_ms; |
| period_ms_t last_acquired_timestamp_ms; |
| period_ms_t last_released_timestamp_ms; |
| period_ms_t last_reset_timestamp_ms; |
| int last_acquired_error; |
| int last_released_error; |
| } wakelock_stats_t; |
| |
| static wakelock_stats_t wakelock_stats; |
| |
| // This mutex ensures that the functions that update and dump the statistics |
| // are executed serially. |
| static pthread_mutex_t monitor; |
| |
| static bt_status_t wakelock_acquire_callout(void); |
| static bt_status_t wakelock_acquire_native(void); |
| static bt_status_t wakelock_release_callout(void); |
| static bt_status_t wakelock_release_native(void); |
| static void wakelock_initialize(void); |
| static void wakelock_initialize_native(void); |
| static void reset_wakelock_stats(void); |
| static void update_wakelock_acquired_stats(bt_status_t acquired_status); |
| static void update_wakelock_released_stats(bt_status_t released_status); |
| |
| void wakelock_set_os_callouts(bt_os_callouts_t *callouts) |
| { |
| wakelock_os_callouts = callouts; |
| is_native = (wakelock_os_callouts == NULL); |
| LOG_INFO(LOG_TAG, "%s set to %s", |
| __func__, (is_native)? "native" : "non-native"); |
| } |
| |
| bool wakelock_acquire(void) { |
| pthread_once(&initialized, wakelock_initialize); |
| |
| bt_status_t status = BT_STATUS_FAIL; |
| |
| if (is_native) |
| status = wakelock_acquire_native(); |
| else |
| status = wakelock_acquire_callout(); |
| |
| update_wakelock_acquired_stats(status); |
| |
| if (status != BT_STATUS_SUCCESS) |
| LOG_ERROR(LOG_TAG, "%s unable to acquire wake lock: %d", __func__, status); |
| |
| return (status == BT_STATUS_SUCCESS); |
| } |
| |
| static bt_status_t wakelock_acquire_callout(void) { |
| return static_cast<bt_status_t>(wakelock_os_callouts->acquire_wake_lock(WAKE_LOCK_ID)); |
| } |
| |
| static bt_status_t wakelock_acquire_native(void) { |
| if (wake_lock_fd == INVALID_FD) { |
| LOG_ERROR(LOG_TAG, "%s lock not acquired, invalid fd", __func__); |
| return BT_STATUS_PARM_INVALID; |
| } |
| |
| if (wake_unlock_fd == INVALID_FD) { |
| LOG_ERROR(LOG_TAG, "%s not acquiring lock: can't release lock", __func__); |
| return BT_STATUS_PARM_INVALID; |
| } |
| |
| long lock_name_len = strlen(WAKE_LOCK_ID); |
| locked_id_len = write(wake_lock_fd, WAKE_LOCK_ID, lock_name_len); |
| if (locked_id_len == -1) { |
| LOG_ERROR(LOG_TAG, "%s wake lock not acquired: %s", |
| __func__, strerror(errno)); |
| return BT_STATUS_FAIL; |
| } else if (locked_id_len < lock_name_len) { |
| // TODO (jamuraa): this is weird. maybe we should release and retry. |
| LOG_WARN(LOG_TAG, "%s wake lock truncated to %zd chars", |
| __func__, locked_id_len); |
| } |
| return BT_STATUS_SUCCESS; |
| } |
| |
| bool wakelock_release(void) { |
| pthread_once(&initialized, wakelock_initialize); |
| |
| bt_status_t status = BT_STATUS_FAIL; |
| |
| if (is_native) |
| status = wakelock_release_native(); |
| else |
| status = wakelock_release_callout(); |
| |
| update_wakelock_released_stats(status); |
| |
| return (status == BT_STATUS_SUCCESS); |
| } |
| |
| static bt_status_t wakelock_release_callout(void) { |
| return static_cast<bt_status_t>(wakelock_os_callouts->release_wake_lock(WAKE_LOCK_ID)); |
| } |
| |
| static bt_status_t wakelock_release_native(void) { |
| if (wake_unlock_fd == INVALID_FD) { |
| LOG_ERROR(LOG_TAG, "%s lock not released, invalid fd", __func__); |
| return BT_STATUS_PARM_INVALID; |
| } |
| |
| ssize_t wrote_name_len = write(wake_unlock_fd, WAKE_LOCK_ID, locked_id_len); |
| if (wrote_name_len == -1) { |
| LOG_ERROR(LOG_TAG, "%s can't release wake lock: %s", |
| __func__, strerror(errno)); |
| } else if (wrote_name_len < locked_id_len) { |
| LOG_ERROR(LOG_TAG, "%s lock release only wrote %zd, assuming released", |
| __func__, wrote_name_len); |
| } |
| return BT_STATUS_SUCCESS; |
| } |
| |
| static void wakelock_initialize(void) { |
| pthread_mutex_init(&monitor, NULL); |
| reset_wakelock_stats(); |
| |
| if (is_native) |
| wakelock_initialize_native(); |
| } |
| |
| static void wakelock_initialize_native(void) { |
| LOG_DEBUG(LOG_TAG, "%s opening wake locks", __func__); |
| |
| if (wake_lock_path.empty()) |
| wake_lock_path = DEFAULT_WAKE_LOCK_PATH; |
| |
| wake_lock_fd = open(wake_lock_path.c_str(), O_RDWR | O_CLOEXEC); |
| if (wake_lock_fd == INVALID_FD) { |
| LOG_ERROR(LOG_TAG, "%s can't open wake lock %s: %s", |
| __func__, wake_lock_path.c_str(), strerror(errno)); |
| } |
| |
| if (wake_unlock_path.empty()) |
| wake_unlock_path = DEFAULT_WAKE_UNLOCK_PATH; |
| |
| wake_unlock_fd = open(wake_unlock_path.c_str(), O_RDWR | O_CLOEXEC); |
| if (wake_unlock_fd == INVALID_FD) { |
| LOG_ERROR(LOG_TAG, "%s can't open wake unlock %s: %s", |
| __func__, wake_unlock_path.c_str(), strerror(errno)); |
| } |
| } |
| |
| void wakelock_cleanup(void) { |
| wake_lock_path.clear(); |
| wake_unlock_path.clear(); |
| initialized = PTHREAD_ONCE_INIT; |
| pthread_mutex_destroy(&monitor); |
| } |
| |
| void wakelock_set_paths(const char *lock_path, const char *unlock_path) { |
| if (lock_path) |
| wake_lock_path = lock_path; |
| |
| if (unlock_path) |
| wake_unlock_path = unlock_path; |
| } |
| |
| static period_ms_t now(void) { |
| struct timespec ts; |
| if (clock_gettime(CLOCK_ID, &ts) == -1) { |
| LOG_ERROR(LOG_TAG, "%s unable to get current time: %s", |
| __func__, strerror(errno)); |
| return 0; |
| } |
| |
| return (ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL); |
| } |
| |
| // Reset the Bluetooth wakelock statistics. |
| // This function is thread-safe. |
| static void reset_wakelock_stats(void) { |
| pthread_mutex_lock(&monitor); |
| |
| wakelock_stats.is_acquired = false; |
| wakelock_stats.acquired_count = 0; |
| wakelock_stats.released_count = 0; |
| wakelock_stats.acquired_errors = 0; |
| wakelock_stats.released_errors = 0; |
| wakelock_stats.min_acquired_interval_ms = 0; |
| wakelock_stats.max_acquired_interval_ms = 0; |
| wakelock_stats.last_acquired_interval_ms = 0; |
| wakelock_stats.total_acquired_interval_ms = 0; |
| wakelock_stats.last_acquired_timestamp_ms = 0; |
| wakelock_stats.last_released_timestamp_ms = 0; |
| wakelock_stats.last_reset_timestamp_ms = now(); |
| |
| pthread_mutex_unlock(&monitor); |
| } |
| |
| // |
| // Update the Bluetooth acquire wakelock statistics. |
| // |
| // This function should be called every time when the wakelock is acquired. |
| // |acquired_status| is the status code that was return when the wakelock was |
| // acquired. |
| // This function is thread-safe. |
| // |
| static void update_wakelock_acquired_stats(bt_status_t acquired_status) { |
| const period_ms_t now_ms = now(); |
| |
| pthread_mutex_lock(&monitor); |
| |
| if (acquired_status != BT_STATUS_SUCCESS) { |
| wakelock_stats.acquired_errors++; |
| wakelock_stats.last_acquired_error = acquired_status; |
| } |
| |
| if (wakelock_stats.is_acquired) { |
| pthread_mutex_unlock(&monitor); |
| return; |
| } |
| |
| wakelock_stats.is_acquired = true; |
| wakelock_stats.acquired_count++; |
| wakelock_stats.last_acquired_timestamp_ms = now_ms; |
| |
| pthread_mutex_unlock(&monitor); |
| |
| metrics_wake_event(WAKE_EVENT_ACQUIRED, NULL, WAKE_LOCK_ID, now_ms); |
| } |
| |
| // |
| // Update the Bluetooth release wakelock statistics. |
| // |
| // This function should be called every time when the wakelock is released. |
| // |released_status| is the status code that was return when the wakelock was |
| // released. |
| // This function is thread-safe. |
| // |
| static void update_wakelock_released_stats(bt_status_t released_status) { |
| const period_ms_t now_ms = now(); |
| |
| pthread_mutex_lock(&monitor); |
| |
| if (released_status != BT_STATUS_SUCCESS) { |
| wakelock_stats.released_errors++; |
| wakelock_stats.last_released_error = released_status; |
| } |
| |
| if (!wakelock_stats.is_acquired) { |
| pthread_mutex_unlock(&monitor); |
| return; |
| } |
| |
| wakelock_stats.is_acquired = false; |
| wakelock_stats.released_count++; |
| wakelock_stats.last_released_timestamp_ms = now_ms; |
| |
| // Compute the acquired interval and update the statistics |
| period_ms_t delta_ms = now_ms - wakelock_stats.last_acquired_timestamp_ms; |
| if (delta_ms < wakelock_stats.min_acquired_interval_ms || |
| wakelock_stats.released_count == 1) { |
| wakelock_stats.min_acquired_interval_ms = delta_ms; |
| } |
| if (delta_ms > wakelock_stats.max_acquired_interval_ms) { |
| wakelock_stats.max_acquired_interval_ms = delta_ms; |
| } |
| wakelock_stats.last_acquired_interval_ms = delta_ms; |
| wakelock_stats.total_acquired_interval_ms += delta_ms; |
| |
| pthread_mutex_unlock(&monitor); |
| |
| metrics_wake_event(WAKE_EVENT_RELEASED, NULL, WAKE_LOCK_ID, now_ms); |
| } |
| |
| void wakelock_debug_dump(int fd) { |
| const period_ms_t now_ms = now(); |
| |
| // Need to keep track for lock errors - e.g., the "monitor" mutex |
| // might not be initialized |
| const int lock_error = pthread_mutex_lock(&monitor); |
| |
| // Compute the last acquired interval if the wakelock is still acquired |
| period_ms_t delta_ms = 0; |
| period_ms_t last_interval = wakelock_stats.last_acquired_interval_ms; |
| period_ms_t min_interval = wakelock_stats.min_acquired_interval_ms; |
| period_ms_t max_interval = wakelock_stats.max_acquired_interval_ms; |
| period_ms_t ave_interval = 0; |
| |
| if (wakelock_stats.is_acquired) { |
| delta_ms = now_ms - wakelock_stats.last_acquired_timestamp_ms; |
| if (delta_ms > max_interval) |
| max_interval = delta_ms; |
| if (delta_ms < min_interval) |
| min_interval = delta_ms; |
| last_interval = delta_ms; |
| } |
| period_ms_t total_interval = |
| wakelock_stats.total_acquired_interval_ms + delta_ms; |
| |
| if (wakelock_stats.acquired_count > 0) |
| ave_interval = total_interval / wakelock_stats.acquired_count; |
| |
| dprintf(fd, "\nBluetooth Wakelock Statistics:\n"); |
| dprintf(fd, " Is acquired : %s\n", |
| wakelock_stats.is_acquired? "true" : "false"); |
| dprintf(fd, " Acquired/released count : %zu / %zu\n", |
| wakelock_stats.acquired_count, wakelock_stats.released_count); |
| dprintf(fd, " Acquired/released error count : %zu / %zu\n", |
| wakelock_stats.acquired_errors, wakelock_stats.released_errors); |
| dprintf(fd, " Last acquire/release error code: %d / %d\n", |
| wakelock_stats.last_acquired_error, wakelock_stats.last_released_error); |
| dprintf(fd, " Last acquired time (ms) : %llu\n", |
| (unsigned long long)last_interval); |
| dprintf(fd, " Acquired time min/max/avg (ms) : %llu / %llu / %llu\n", |
| (unsigned long long)min_interval, (unsigned long long)max_interval, |
| (unsigned long long)ave_interval); |
| dprintf(fd, " Total acquired time (ms) : %llu\n", |
| (unsigned long long)total_interval); |
| dprintf(fd, " Total run time (ms) : %llu\n", |
| (unsigned long long)(now_ms - wakelock_stats.last_reset_timestamp_ms)); |
| |
| if (lock_error == 0) |
| pthread_mutex_unlock(&monitor); |
| } |