/* Copyright (c) 2012, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #define __INIT_HEAD(x) { .head = RB_ROOT,\ .next = NULL, } #define DEFINE_TIME_HEAD(x) struct timerqueue_head x = __INIT_HEAD(x) /** * struct event_timer_info - basic event timer structure * @node: timerqueue node to track time ordered data structure * of event timers * @timer: hrtimer created for this event. * @function : callback function for event timer. * @data : callback data for event timer. */ struct event_timer_info { struct timerqueue_node node; void (*function)(void *); void *data; }; static DEFINE_TIME_HEAD(timer_head); static DEFINE_SPINLOCK(event_timer_lock); static DEFINE_SPINLOCK(event_setup_lock); static struct hrtimer event_hrtimer; static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer); static int msm_event_debug_mask; module_param_named( debug_mask, msm_event_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP ); enum { MSM_EVENT_TIMER_DEBUG = 1U << 0, }; /** * add_event_timer() : Add a wakeup event. Intended to be called * by clients once. Returns a handle to be used * for future transactions. * @function : The callback function will be called when event * timer expires. * @data: callback data provided by client. */ struct event_timer_info *add_event_timer(void (*function)(void *), void *data) { struct event_timer_info *event_info = kzalloc(sizeof(struct event_timer_info), GFP_KERNEL); if (!event_info) return NULL; event_info->function = function; event_info->data = data; /* Init rb node and hr timer */ timerqueue_init(&event_info->node); pr_debug("%s: New Event Added. Event 0x%x.", __func__, (unsigned int)event_info); return event_info; } /** * is_event_next(): Helper function to check if the event is the next * next expiring event * @event : handle to the event to be checked. */ static bool is_event_next(struct event_timer_info *event) { struct event_timer_info *next_event; struct timerqueue_node *next; bool ret = false; next = timerqueue_getnext(&timer_head); if (!next) goto exit_is_next_event; next_event = container_of(next, struct event_timer_info, node); if (!next_event) goto exit_is_next_event; if (next_event == event) ret = true; exit_is_next_event: return ret; } /** * is_event_active(): Helper function to check if the timer for a given event * has been started. * @event : handle to the event to be checked. */ static bool is_event_active(struct event_timer_info *event) { struct timerqueue_node *next; struct event_timer_info *next_event; bool ret = false; for (next = timerqueue_getnext(&timer_head); next; next = timerqueue_iterate_next(next)) { next_event = container_of(next, struct event_timer_info, node); if (event == next_event) { ret = true; break; } } return ret; } /** * create_httimer(): Helper function to setup hrtimer. */ static void create_hrtimer(ktime_t expires) { static bool timer_initialized; if (!timer_initialized) { hrtimer_init(&event_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); timer_initialized = true; } event_hrtimer.function = event_hrtimer_cb; hrtimer_start(&event_hrtimer, expires, HRTIMER_MODE_ABS); } /** * event_hrtimer_cb() : Callback function for hr timer. * Make the client CB from here and remove the event * from the time ordered queue. */ static enum hrtimer_restart event_hrtimer_cb(struct hrtimer *hrtimer) { struct event_timer_info *event; struct timerqueue_node *next; unsigned long flags; spin_lock_irqsave(&event_timer_lock, flags); next = timerqueue_getnext(&timer_head); while (next && (ktime_to_ns(next->expires) <= ktime_to_ns(hrtimer->node.expires))) { if (!next) goto hrtimer_cb_exit; event = container_of(next, struct event_timer_info, node); if (!event) goto hrtimer_cb_exit; if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) pr_info("%s: Deleting event 0x%x @ %lu", __func__, (unsigned int)event, (unsigned long)ktime_to_ns(next->expires)); timerqueue_del(&timer_head, &event->node); if (event->function) event->function(event->data); next = timerqueue_getnext(&timer_head); } if (next) create_hrtimer(next->expires); spin_unlock_irqrestore(&event_timer_lock, flags); hrtimer_cb_exit: return HRTIMER_NORESTART; } /** * create_timer_smp(): Helper function used setting up timer on core 0. */ static void create_timer_smp(void *data) { unsigned long flags; struct event_timer_info *event = (struct event_timer_info *)data; struct timerqueue_node *next; spin_lock_irqsave(&event_timer_lock, flags); if (is_event_active(event)) timerqueue_del(&timer_head, &event->node); next = timerqueue_getnext(&timer_head); timerqueue_add(&timer_head, &event->node); if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) pr_info("%s: Adding Event 0x%x for %lu", __func__, (unsigned int)event, (unsigned long)ktime_to_ns(event->node.expires)); if (!next || (next && (ktime_to_ns(event->node.expires) < ktime_to_ns(next->expires)))) { if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) pr_info("%s: Setting timer for %lu", __func__, (unsigned long)ktime_to_ns(event->node.expires)); create_hrtimer(event->node.expires); } spin_unlock_irqrestore(&event_timer_lock, flags); } /** * setup_timer() : Helper function to setup timer on primary * core during hrtimer callback. * @event: event handle causing the wakeup. */ static void setup_event_hrtimer(struct event_timer_info *event) { smp_call_function_single(0, create_timer_smp, event, 1); } /** * activate_event_timer() : Set the expiration time for an event in absolute * ktime. This is a oneshot event timer, clients * should call this again to set another expiration. * @event : event handle. * @event_time : event time in absolute ktime. */ void activate_event_timer(struct event_timer_info *event, ktime_t event_time) { if (!event) return; if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) pr_info("%s: Adding event timer @ %lu", __func__, (unsigned long)ktime_to_us(event_time)); spin_lock(&event_setup_lock); event->node.expires = event_time; /* Start hr timer and add event to rb tree */ setup_event_hrtimer(event); spin_unlock(&event_setup_lock); } /** * deactivate_event_timer() : Deactivate an event timer, this removes the event from * the time ordered queue of event timers. * @event: event handle. */ void deactivate_event_timer(struct event_timer_info *event) { unsigned long flags; if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) pr_info("%s: Deactivate timer", __func__); spin_lock_irqsave(&event_timer_lock, flags); if (is_event_active(event)) { if (is_event_next(event)) hrtimer_try_to_cancel(&event_hrtimer); timerqueue_del(&timer_head, &event->node); } spin_unlock_irqrestore(&event_timer_lock, flags); } /** * destroy_event_timer() : Free the event info data structure allocated during * add_event_timer(). * @event: event handle. */ void destroy_event_timer(struct event_timer_info *event) { unsigned long flags; spin_lock_irqsave(&event_timer_lock, flags); if (is_event_active(event)) { if (is_event_next(event)) hrtimer_try_to_cancel(&event_hrtimer); timerqueue_del(&timer_head, &event->node); } spin_unlock_irqrestore(&event_timer_lock, flags); kfree(event); } /** * get_next_event_timer() - Get the next wakeup event. Returns * a ktime value of the next expiring event. */ ktime_t get_next_event_time(void) { unsigned long flags; struct timerqueue_node *next; ktime_t next_event = ns_to_ktime(0); spin_lock_irqsave(&event_timer_lock, flags); next = timerqueue_getnext(&timer_head); spin_unlock_irqrestore(&event_timer_lock, flags); if (!next) return next_event; next_event = hrtimer_get_remaining(&event_hrtimer); if (msm_event_debug_mask && MSM_EVENT_TIMER_DEBUG) pr_info("%s: Next Event %lu", __func__, (unsigned long)ktime_to_us(next_event)); return next_event; }