alarm.cc revision 0f9b91e150e153229235c163861198e23600e636
1/****************************************************************************** 2 * 3 * Copyright (C) 2014 Google, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at: 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 ******************************************************************************/ 18 19#define LOG_TAG "bt_osi_alarm" 20 21#include <assert.h> 22#include <errno.h> 23#include <hardware/bluetooth.h> 24#include <inttypes.h> 25#include <time.h> 26 27#include "osi/include/allocator.h" 28#include "osi/include/alarm.h" 29#include "osi/include/list.h" 30#include "osi/include/osi.h" 31#include "osi/include/log.h" 32 33struct alarm_t { 34 // The lock is held while the callback for this alarm is being executed. 35 // It allows us to release the coarse-grained monitor lock while a potentially 36 // long-running callback is executing. |alarm_cancel| uses this lock to provide 37 // a guarantee to its caller that the callback will not be in progress when it 38 // returns. 39 pthread_mutex_t callback_lock; 40 period_ms_t deadline; 41 alarm_callback_t callback; 42 bool periodic; 43 period_ms_t period; 44 void *data; 45}; 46 47extern bt_os_callouts_t *bt_os_callouts; 48 49// If the next wakeup time is less than this threshold, we should acquire 50// a wakelock instead of setting a wake alarm so we're not bouncing in 51// and out of suspend frequently. This value is externally visible to allow 52// unit tests to run faster. It should not be modified by production code. 53int64_t TIMER_INTERVAL_FOR_WAKELOCK_IN_MS = 3000; 54static const clockid_t CLOCK_ID = CLOCK_BOOTTIME; 55static const char *WAKE_LOCK_ID = "bluedroid_timer"; 56 57// This mutex ensures that the |alarm_set|, |alarm_cancel|, and alarm callback 58// functions execute serially and not concurrently. As a result, this mutex also 59// protects the |alarms| list. 60static pthread_mutex_t monitor; 61static list_t *alarms; 62static timer_t timer; 63static bool timer_set; 64 65static bool lazy_initialize(void); 66static period_ms_t now(void); 67static void alarm_set_internal(alarm_t *alarm, period_ms_t deadline, alarm_callback_t cb, void *data, bool is_periodic); 68static void schedule(alarm_t *alarm, period_ms_t deadline); 69static void timer_callback(void *data); 70static void reschedule(void); 71 72alarm_t *alarm_new(void) { 73 // Make sure we have a list we can insert alarms into. 74 if (!alarms && !lazy_initialize()) 75 return NULL; 76 77 pthread_mutexattr_t attr; 78 pthread_mutexattr_init(&attr); 79 80 alarm_t *ret = osi_calloc(sizeof(alarm_t)); 81 if (!ret) { 82 LOG_ERROR("%s unable to allocate memory for alarm.", __func__); 83 goto error; 84 } 85 86 // Make this a recursive mutex to make it safe to call |alarm_cancel| from 87 // within the callback function of the alarm. 88 int error = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 89 if (error) { 90 LOG_ERROR("%s unable to create a recursive mutex: %s", __func__, strerror(error)); 91 goto error; 92 } 93 94 error = pthread_mutex_init(&ret->callback_lock, &attr); 95 if (error) { 96 LOG_ERROR("%s unable to initialize mutex: %s", __func__, strerror(error)); 97 goto error; 98 } 99 100 pthread_mutexattr_destroy(&attr); 101 return ret; 102 103error:; 104 pthread_mutexattr_destroy(&attr); 105 osi_free(ret); 106 return NULL; 107} 108 109void alarm_free(alarm_t *alarm) { 110 if (!alarm) 111 return; 112 113 alarm_cancel(alarm); 114 pthread_mutex_destroy(&alarm->callback_lock); 115 osi_free(alarm); 116} 117 118void alarm_set(alarm_t *alarm, period_ms_t deadline, alarm_callback_t cb, void *data) { 119 alarm_set_internal(alarm, deadline, cb, data, false); 120} 121 122void alarm_set_periodic(alarm_t *alarm, period_ms_t period, alarm_callback_t cb, void *data) { 123 alarm_set_internal(alarm, period, cb, data, true); 124} 125 126// Runs in exclusion with alarm_cancel and timer_callback. 127static void alarm_set_internal(alarm_t *alarm, period_ms_t deadline, alarm_callback_t cb, void *data, bool is_periodic) { 128 assert(alarms != NULL); 129 assert(alarm != NULL); 130 assert(cb != NULL); 131 132 pthread_mutex_lock(&monitor); 133 134 alarm->periodic = is_periodic; 135 alarm->period = deadline; 136 alarm->callback = cb; 137 alarm->data = data; 138 139 schedule(alarm, deadline); 140 141 pthread_mutex_unlock(&monitor); 142} 143 144void alarm_cancel(alarm_t *alarm) { 145 assert(alarms != NULL); 146 assert(alarm != NULL); 147 148 pthread_mutex_lock(&monitor); 149 150 bool needs_reschedule = (!list_is_empty(alarms) && list_front(alarms) == alarm); 151 152 list_remove(alarms, alarm); 153 alarm->deadline = 0; 154 alarm->callback = NULL; 155 alarm->data = NULL; 156 157 if (needs_reschedule) 158 reschedule(); 159 160 pthread_mutex_unlock(&monitor); 161 162 // If the callback for |alarm| is in progress, wait here until it completes. 163 pthread_mutex_lock(&alarm->callback_lock); 164 pthread_mutex_unlock(&alarm->callback_lock); 165} 166 167static bool lazy_initialize(void) { 168 assert(alarms == NULL); 169 170 pthread_mutex_init(&monitor, NULL); 171 172 alarms = list_new(NULL); 173 if (!alarms) { 174 LOG_ERROR("%s unable to allocate alarm list.", __func__); 175 return false; 176 } 177 178 return true; 179} 180 181static period_ms_t now(void) { 182 assert(alarms != NULL); 183 184 struct timespec ts; 185 if (clock_gettime(CLOCK_ID, &ts) == -1) { 186 LOG_ERROR("%s unable to get current time: %s", __func__, strerror(errno)); 187 return 0; 188 } 189 190 return (ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL); 191} 192 193// Must be called with monitor held 194static void schedule(alarm_t *alarm, period_ms_t deadline) { 195 // If the alarm is currently set and it's at the start of the list, 196 // we'll need to re-schedule since we've adjusted the earliest deadline. 197 bool needs_reschedule = (!list_is_empty(alarms) && list_front(alarms) == alarm); 198 if (alarm->callback) 199 list_remove(alarms, alarm); 200 201 alarm->deadline = now() + deadline; 202 203 // Add it into the timer list sorted by deadline (earliest deadline first). 204 if (list_is_empty(alarms) || ((alarm_t *)list_front(alarms))->deadline >= alarm->deadline) 205 list_prepend(alarms, alarm); 206 else 207 for (list_node_t *node = list_begin(alarms); node != list_end(alarms); node = list_next(node)) { 208 list_node_t *next = list_next(node); 209 if (next == list_end(alarms) || ((alarm_t *)list_node(next))->deadline >= alarm->deadline) { 210 list_insert_after(alarms, node, alarm); 211 break; 212 } 213 } 214 215 // If the new alarm has the earliest deadline, we need to re-evaluate our schedule. 216 if (needs_reschedule || (!list_is_empty(alarms) && list_front(alarms) == alarm)) 217 reschedule(); 218} 219 220// Warning: this function is called in the context of an unknown thread. 221// As a result, it must be thread-safe relative to other operations on 222// the alarm list. 223static void timer_callback(void *ptr) { 224 alarm_t *alarm = (alarm_t *)ptr; 225 assert(alarm != NULL); 226 227 pthread_mutex_lock(&monitor); 228 229 bool alarm_valid = list_remove(alarms, alarm); 230 231 // The alarm was cancelled before we got to it. Release the monitor 232 // lock and exit right away since there's nothing left to do. 233 if (!alarm_valid) { 234 reschedule(); 235 pthread_mutex_unlock(&monitor); 236 return; 237 } 238 239 alarm_callback_t callback = alarm->callback; 240 void *data = alarm->data; 241 242 if (alarm->periodic) { 243 schedule(alarm, alarm->period); 244 } else { 245 reschedule(); 246 247 alarm->deadline = 0; 248 alarm->callback = NULL; 249 alarm->data = NULL; 250 } 251 252 // Downgrade lock. 253 pthread_mutex_lock(&alarm->callback_lock); 254 pthread_mutex_unlock(&monitor); 255 256 callback(data); 257 258 pthread_mutex_unlock(&alarm->callback_lock); 259} 260 261// NOTE: must be called with monitor lock. 262static void reschedule(void) { 263 assert(alarms != NULL); 264 265 bool timer_was_set = timer_set; 266 if (timer_set) { 267 timer_delete(timer); 268 timer_set = false; 269 } 270 271 if (list_is_empty(alarms)) { 272 bt_os_callouts->release_wake_lock(WAKE_LOCK_ID); 273 return; 274 } 275 276 alarm_t *next = list_front(alarms); 277 int64_t next_exp = next->deadline - now(); 278 if (next_exp < TIMER_INTERVAL_FOR_WAKELOCK_IN_MS) { 279 if (!timer_was_set) { 280 int status = bt_os_callouts->acquire_wake_lock(WAKE_LOCK_ID); 281 if (status != BT_STATUS_SUCCESS) { 282 LOG_ERROR("%s unable to acquire wake lock: %d", __func__, status); 283 return; 284 } 285 } 286 287 struct sigevent sigevent; 288 memset(&sigevent, 0, sizeof(sigevent)); 289 sigevent.sigev_notify = SIGEV_THREAD; 290 sigevent.sigev_notify_function = (void (*)(union sigval))timer_callback; 291 sigevent.sigev_value.sival_ptr = next; 292 if (timer_create(CLOCK_ID, &sigevent, &timer) == -1) { 293 LOG_ERROR("%s unable to create timer: %s", __func__, strerror(errno)); 294 return; 295 } 296 297 struct itimerspec wakeup_time; 298 memset(&wakeup_time, 0, sizeof(wakeup_time)); 299 wakeup_time.it_value.tv_sec = (next->deadline / 1000); 300 wakeup_time.it_value.tv_nsec = (next->deadline % 1000) * 1000000LL; 301 if (timer_settime(timer, TIMER_ABSTIME, &wakeup_time, NULL) == -1) { 302 LOG_ERROR("%s unable to set timer: %s", __func__, strerror(errno)); 303 timer_delete(timer); 304 return; 305 } 306 timer_set = true; 307 } else { 308 if (!bt_os_callouts->set_wake_alarm(next_exp, true, timer_callback, next)) 309 LOG_ERROR("%s unable to set wake alarm for %" PRId64 "ms.", __func__, next_exp); 310 311 bt_os_callouts->release_wake_lock(WAKE_LOCK_ID); 312 } 313} 314