summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Stultz <john.stultz@linaro.org>2011-08-10 12:41:36 -0700
committerJohn Stultz <john.stultz@linaro.org>2011-08-10 14:55:29 -0700
commit9082c465a5403f4a98734193e078552991a2e283 (patch)
tree387e564f326488abe9ee84b0c723294ea573de1e
parenta28cde81ab13cc251748a4c4ef06883dd09a10ea (diff)
alarmtimers: Add try_to_cancel functionality
There's a number of edge cases when cancelling a alarm, so to be sure we accurately do so, introduce try_to_cancel, which returns proper failure errors if it cannot. Also modify cancel to spin until the alarm is properly disabled. CC: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: John Stultz <john.stultz@linaro.org>
-rw-r--r--include/linux/alarmtimer.h3
-rw-r--r--kernel/time/alarmtimer.c43
2 files changed, 39 insertions, 7 deletions
diff --git a/include/linux/alarmtimer.h b/include/linux/alarmtimer.h
index 304124a6c982..975009e1cbe6 100644
--- a/include/linux/alarmtimer.h
+++ b/include/linux/alarmtimer.h
@@ -44,7 +44,8 @@ struct alarm {
void alarm_init(struct alarm *alarm, enum alarmtimer_type type,
enum alarmtimer_restart (*function)(struct alarm *, ktime_t));
void alarm_start(struct alarm *alarm, ktime_t start);
-void alarm_cancel(struct alarm *alarm);
+int alarm_try_to_cancel(struct alarm *alarm);
+int alarm_cancel(struct alarm *alarm);
u64 alarm_forward(struct alarm *alarm, ktime_t now, ktime_t interval);
diff --git a/kernel/time/alarmtimer.c b/kernel/time/alarmtimer.c
index 5b14cc29b6a6..bdb7342b6896 100644
--- a/kernel/time/alarmtimer.c
+++ b/kernel/time/alarmtimer.c
@@ -336,21 +336,49 @@ void alarm_start(struct alarm *alarm, ktime_t start)
}
/**
- * alarm_cancel - Tries to cancel an alarm timer
+ * alarm_try_to_cancel - Tries to cancel an alarm timer
* @alarm: ptr to alarm to be canceled
+ *
+ * Returns 1 if the timer was canceled, 0 if it was not running,
+ * and -1 if the callback was running
*/
-void alarm_cancel(struct alarm *alarm)
+int alarm_try_to_cancel(struct alarm *alarm)
{
struct alarm_base *base = &alarm_bases[alarm->type];
unsigned long flags;
-
+ int ret = -1;
spin_lock_irqsave(&base->lock, flags);
- if (alarmtimer_is_queued(alarm))
+
+ if (alarmtimer_callback_running(alarm))
+ goto out;
+
+ if (alarmtimer_is_queued(alarm)) {
alarmtimer_remove(base, alarm);
+ ret = 1;
+ } else
+ ret = 0;
+out:
spin_unlock_irqrestore(&base->lock, flags);
+ return ret;
}
+/**
+ * alarm_cancel - Spins trying to cancel an alarm timer until it is done
+ * @alarm: ptr to alarm to be canceled
+ *
+ * Returns 1 if the timer was canceled, 0 if it was not active.
+ */
+int alarm_cancel(struct alarm *alarm)
+{
+ for (;;) {
+ int ret = alarm_try_to_cancel(alarm);
+ if (ret >= 0)
+ return ret;
+ cpu_relax();
+ }
+}
+
u64 alarm_forward(struct alarm *alarm, ktime_t now, ktime_t interval)
{
@@ -510,7 +538,9 @@ static int alarm_timer_del(struct k_itimer *timr)
if (!rtcdev)
return -ENOTSUPP;
- alarm_cancel(&timr->it.alarm.alarmtimer);
+ if (alarm_try_to_cancel(&timr->it.alarm.alarmtimer) < 0)
+ return TIMER_RETRY;
+
return 0;
}
@@ -534,7 +564,8 @@ static int alarm_timer_set(struct k_itimer *timr, int flags,
alarm_timer_get(timr, old_setting);
/* If the timer was already set, cancel it */
- alarm_cancel(&timr->it.alarm.alarmtimer);
+ if (alarm_try_to_cancel(&timr->it.alarm.alarmtimer) < 0)
+ return TIMER_RETRY;
/* start the timer */
timr->it.alarm.interval = timespec_to_ktime(new_setting->it_interval);