/* drivers/rtc/alarm-dev.c * * Copyright (C) 2007-2009 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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 #include #include #include #include #include "android_alarm.h" #define ANDROID_ALARM_PRINT_INFO (1U << 0) #define ANDROID_ALARM_PRINT_IO (1U << 1) #define ANDROID_ALARM_PRINT_INT (1U << 2) static int debug_mask = ANDROID_ALARM_PRINT_INFO; module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); #define alarm_dbg(debug_level_mask, fmt, ...) \ do { \ if (debug_mask & ANDROID_ALARM_PRINT_##debug_level_mask) \ pr_info(fmt, ##__VA_ARGS__); \ } while (0) #define ANDROID_ALARM_WAKEUP_MASK ( \ ANDROID_ALARM_RTC_WAKEUP_MASK | \ ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP_MASK) static int alarm_opened; static DEFINE_SPINLOCK(alarm_slock); static struct wakeup_source alarm_wake_lock; static DECLARE_WAIT_QUEUE_HEAD(alarm_wait_queue); static uint32_t alarm_pending; static uint32_t alarm_enabled; static uint32_t wait_pending; struct devalarm { union { struct hrtimer hrt; struct alarm alrm; } u; enum android_alarm_type type; }; static struct devalarm alarms[ANDROID_ALARM_TYPE_COUNT]; static int is_wakeup(enum android_alarm_type type) { return (type == ANDROID_ALARM_RTC_WAKEUP || type == ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP); } static void devalarm_start(struct devalarm *alrm, ktime_t exp) { if (is_wakeup(alrm->type)) alarm_start(&alrm->u.alrm, exp); else hrtimer_start(&alrm->u.hrt, exp, HRTIMER_MODE_ABS); } static int devalarm_try_to_cancel(struct devalarm *alrm) { if (is_wakeup(alrm->type)) return alarm_try_to_cancel(&alrm->u.alrm); return hrtimer_try_to_cancel(&alrm->u.hrt); } static void devalarm_cancel(struct devalarm *alrm) { if (is_wakeup(alrm->type)) alarm_cancel(&alrm->u.alrm); else hrtimer_cancel(&alrm->u.hrt); } static void alarm_clear(enum android_alarm_type alarm_type) { uint32_t alarm_type_mask = 1U << alarm_type; unsigned long flags; spin_lock_irqsave(&alarm_slock, flags); alarm_dbg(IO, "alarm %d clear\n", alarm_type); devalarm_try_to_cancel(&alarms[alarm_type]); if (alarm_pending) { alarm_pending &= ~alarm_type_mask; if (!alarm_pending && !wait_pending) __pm_relax(&alarm_wake_lock); } alarm_enabled &= ~alarm_type_mask; spin_unlock_irqrestore(&alarm_slock, flags); } static void alarm_set(enum android_alarm_type alarm_type, struct timespec *ts) { uint32_t alarm_type_mask = 1U << alarm_type; unsigned long flags; spin_lock_irqsave(&alarm_slock, flags); alarm_dbg(IO, "alarm %d set %ld.%09ld\n", alarm_type, ts->tv_sec, ts->tv_nsec); alarm_enabled |= alarm_type_mask; devalarm_start(&alarms[alarm_type], timespec_to_ktime(*ts)); spin_unlock_irqrestore(&alarm_slock, flags); } static int alarm_wait(void) { unsigned long flags; int rv = 0; spin_lock_irqsave(&alarm_slock, flags); alarm_dbg(IO, "alarm wait\n"); if (!alarm_pending && wait_pending) { __pm_relax(&alarm_wake_lock); wait_pending = 0; } spin_unlock_irqrestore(&alarm_slock, flags); rv = wait_event_interruptible(alarm_wait_queue, alarm_pending); if (rv) return rv; spin_lock_irqsave(&alarm_slock, flags); rv = alarm_pending; wait_pending = 1; alarm_pending = 0; spin_unlock_irqrestore(&alarm_slock, flags); return rv; } static int alarm_set_rtc(struct timespec *ts) { struct rtc_time new_rtc_tm; struct rtc_device *rtc_dev; unsigned long flags; int rv = 0; rtc_time_to_tm(ts->tv_sec, &new_rtc_tm); rtc_dev = alarmtimer_get_rtcdev(); rv = do_settimeofday(ts); if (rv < 0) return rv; if (rtc_dev) rv = rtc_set_time(rtc_dev, &new_rtc_tm); spin_lock_irqsave(&alarm_slock, flags); alarm_pending |= ANDROID_ALARM_TIME_CHANGE_MASK; wake_up(&alarm_wait_queue); spin_unlock_irqrestore(&alarm_slock, flags); return rv; } static int alarm_get_time(enum android_alarm_type alarm_type, struct timespec *ts) { int rv = 0; switch (alarm_type) { case ANDROID_ALARM_RTC_WAKEUP: case ANDROID_ALARM_RTC: getnstimeofday(ts); break; case ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP: case ANDROID_ALARM_ELAPSED_REALTIME: get_monotonic_boottime(ts); break; case ANDROID_ALARM_SYSTEMTIME: ktime_get_ts(ts); break; default: rv = -EINVAL; } return rv; } static long alarm_do_ioctl(struct file *file, unsigned int cmd, struct timespec *ts) { int rv = 0; unsigned long flags; enum android_alarm_type alarm_type = ANDROID_ALARM_IOCTL_TO_TYPE(cmd); if (alarm_type >= ANDROID_ALARM_TYPE_COUNT) return -EINVAL; if (ANDROID_ALARM_BASE_CMD(cmd) != ANDROID_ALARM_GET_TIME(0)) { if ((file->f_flags & O_ACCMODE) == O_RDONLY) return -EPERM; if (file->private_data == NULL && cmd != ANDROID_ALARM_SET_RTC) { spin_lock_irqsave(&alarm_slock, flags); if (alarm_opened) { spin_unlock_irqrestore(&alarm_slock, flags); return -EBUSY; } alarm_opened = 1; file->private_data = (void *)1; spin_unlock_irqrestore(&alarm_slock, flags); } } switch (ANDROID_ALARM_BASE_CMD(cmd)) { case ANDROID_ALARM_CLEAR(0): alarm_clear(alarm_type); break; case ANDROID_ALARM_SET(0): alarm_set(alarm_type, ts); break; case ANDROID_ALARM_SET_AND_WAIT(0): alarm_set(alarm_type, ts); /* fall though */ case ANDROID_ALARM_WAIT: rv = alarm_wait(); break; case ANDROID_ALARM_SET_RTC: rv = alarm_set_rtc(ts); break; case ANDROID_ALARM_GET_TIME(0): rv = alarm_get_time(alarm_type, ts); break; default: rv = -EINVAL; } return rv; } static long alarm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct timespec ts; int rv; switch (ANDROID_ALARM_BASE_CMD(cmd)) { case ANDROID_ALARM_SET_AND_WAIT(0): case ANDROID_ALARM_SET(0): case ANDROID_ALARM_SET_RTC: if (copy_from_user(&ts, (void __user *)arg, sizeof(ts))) return -EFAULT; break; } rv = alarm_do_ioctl(file, cmd, &ts); if (rv) return rv; switch (ANDROID_ALARM_BASE_CMD(cmd)) { case ANDROID_ALARM_GET_TIME(0): if (copy_to_user((void __user *)arg, &ts, sizeof(ts))) return -EFAULT; break; } return 0; } #ifdef CONFIG_COMPAT static long alarm_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct timespec ts; int rv; switch (ANDROID_ALARM_BASE_CMD(cmd)) { case ANDROID_ALARM_SET_AND_WAIT_COMPAT(0): case ANDROID_ALARM_SET_COMPAT(0): case ANDROID_ALARM_SET_RTC_COMPAT: if (compat_get_timespec(&ts, (void __user *)arg)) return -EFAULT; /* fall through */ case ANDROID_ALARM_GET_TIME_COMPAT(0): cmd = ANDROID_ALARM_COMPAT_TO_NORM(cmd); break; } rv = alarm_do_ioctl(file, cmd, &ts); if (rv) return rv; switch (ANDROID_ALARM_BASE_CMD(cmd)) { case ANDROID_ALARM_GET_TIME(0): /* NOTE: we modified cmd above */ if (compat_put_timespec(&ts, (void __user *)arg)) return -EFAULT; break; } return 0; } #endif static int alarm_open(struct inode *inode, struct file *file) { file->private_data = NULL; return 0; } static int alarm_release(struct inode *inode, struct file *file) { int i; unsigned long flags; spin_lock_irqsave(&alarm_slock, flags); if (file->private_data) { for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) { uint32_t alarm_type_mask = 1U << i; if (alarm_enabled & alarm_type_mask) { alarm_dbg(INFO, "%s: clear alarm, pending %d\n", __func__, !!(alarm_pending & alarm_type_mask)); alarm_enabled &= ~alarm_type_mask; } spin_unlock_irqrestore(&alarm_slock, flags); devalarm_cancel(&alarms[i]); spin_lock_irqsave(&alarm_slock, flags); } if (alarm_pending | wait_pending) { if (alarm_pending) alarm_dbg(INFO, "%s: clear pending alarms %x\n", __func__, alarm_pending); __pm_relax(&alarm_wake_lock); wait_pending = 0; alarm_pending = 0; } alarm_opened = 0; } spin_unlock_irqrestore(&alarm_slock, flags); return 0; } static void devalarm_triggered(struct devalarm *alarm) { unsigned long flags; uint32_t alarm_type_mask = 1U << alarm->type; alarm_dbg(INT, "%s: type %d\n", __func__, alarm->type); spin_lock_irqsave(&alarm_slock, flags); if (alarm_enabled & alarm_type_mask) { __pm_wakeup_event(&alarm_wake_lock, 5000); /* 5secs */ alarm_enabled &= ~alarm_type_mask; alarm_pending |= alarm_type_mask; wake_up(&alarm_wait_queue); } spin_unlock_irqrestore(&alarm_slock, flags); } static enum hrtimer_restart devalarm_hrthandler(struct hrtimer *hrt) { struct devalarm *devalrm = container_of(hrt, struct devalarm, u.hrt); devalarm_triggered(devalrm); return HRTIMER_NORESTART; } static enum alarmtimer_restart devalarm_alarmhandler(struct alarm *alrm, ktime_t now) { struct devalarm *devalrm = container_of(alrm, struct devalarm, u.alrm); devalarm_triggered(devalrm); return ALARMTIMER_NORESTART; } static const struct file_operations alarm_fops = { .owner = THIS_MODULE, .unlocked_ioctl = alarm_ioctl, .open = alarm_open, .release = alarm_release, #ifdef CONFIG_COMPAT .compat_ioctl = alarm_compat_ioctl, #endif }; static struct miscdevice alarm_device = { .minor = MISC_DYNAMIC_MINOR, .name = "alarm", .fops = &alarm_fops, }; static int __init alarm_dev_init(void) { int err; int i; err = misc_register(&alarm_device); if (err) return err; alarm_init(&alarms[ANDROID_ALARM_RTC_WAKEUP].u.alrm, ALARM_REALTIME, devalarm_alarmhandler); hrtimer_init(&alarms[ANDROID_ALARM_RTC].u.hrt, CLOCK_REALTIME, HRTIMER_MODE_ABS); alarm_init(&alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].u.alrm, ALARM_BOOTTIME, devalarm_alarmhandler); hrtimer_init(&alarms[ANDROID_ALARM_ELAPSED_REALTIME].u.hrt, CLOCK_BOOTTIME, HRTIMER_MODE_ABS); hrtimer_init(&alarms[ANDROID_ALARM_SYSTEMTIME].u.hrt, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); for (i = 0; i < ANDROID_ALARM_TYPE_COUNT; i++) { alarms[i].type = i; if (!is_wakeup(i)) alarms[i].u.hrt.function = devalarm_hrthandler; } wakeup_source_init(&alarm_wake_lock, "alarm"); return 0; } static void __exit alarm_dev_exit(void) { misc_deregister(&alarm_device); wakeup_source_trash(&alarm_wake_lock); } module_init(alarm_dev_init); module_exit(alarm_dev_exit);