diff options
Diffstat (limited to 'drivers/rtc/rtc-dev.c')
| -rw-r--r-- | drivers/rtc/rtc-dev.c | 402 |
1 files changed, 198 insertions, 204 deletions
diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c index 828b329e08e..d0493936925 100644 --- a/drivers/rtc/rtc-dev.c +++ b/drivers/rtc/rtc-dev.c @@ -11,10 +11,13 @@ * published by the Free Software Foundation. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/rtc.h> +#include <linux/sched.h> +#include "rtc-core.h" -static struct class *rtc_dev_class; static dev_t rtc_devt; #define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */ @@ -26,15 +29,12 @@ static int rtc_dev_open(struct inode *inode, struct file *file) struct rtc_device, char_dev); const struct rtc_class_ops *ops = rtc->ops; - /* We keep the lock as long as the device is in use - * and return immediately if busy - */ - if (!(mutex_trylock(&rtc->char_lock))) + if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags)) return -EBUSY; - file->private_data = &rtc->class_dev; + file->private_data = rtc; - err = ops->open ? ops->open(rtc->class_dev.dev) : 0; + err = ops->open ? ops->open(rtc->dev.parent) : 0; if (err == 0) { spin_lock_irq(&rtc->irq_lock); rtc->irq_data = 0; @@ -43,8 +43,8 @@ static int rtc_dev_open(struct inode *inode, struct file *file) return 0; } - /* something has gone wrong, release the lock */ - mutex_unlock(&rtc->char_lock); + /* something has gone wrong */ + clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags); return err; } @@ -61,10 +61,9 @@ static void rtc_uie_task(struct work_struct *work) int num = 0; int err; - err = rtc_read_time(&rtc->class_dev, &tm); + err = rtc_read_time(rtc, &tm); - local_irq_disable(); - spin_lock(&rtc->irq_lock); + spin_lock_irq(&rtc->irq_lock); if (rtc->stop_uie_polling || err) { rtc->uie_task_active = 0; } else if (rtc->oldsecs != tm.tm_sec) { @@ -77,10 +76,9 @@ static void rtc_uie_task(struct work_struct *work) } else if (schedule_work(&rtc->uie_task) == 0) { rtc->uie_task_active = 0; } - spin_unlock(&rtc->irq_lock); + spin_unlock_irq(&rtc->irq_lock); if (num) - rtc_update_irq(&rtc->class_dev, num, RTC_UF | RTC_IRQF); - local_irq_enable(); + rtc_handle_legacy_irq(rtc, num, RTC_UF); } static void rtc_uie_timer(unsigned long data) { @@ -95,10 +93,10 @@ static void rtc_uie_timer(unsigned long data) spin_unlock_irqrestore(&rtc->irq_lock, flags); } -static void clear_uie(struct rtc_device *rtc) +static int clear_uie(struct rtc_device *rtc) { spin_lock_irq(&rtc->irq_lock); - if (rtc->irq_active) { + if (rtc->uie_irq_active) { rtc->stop_uie_polling = 1; if (rtc->uie_timer_active) { spin_unlock_irq(&rtc->irq_lock); @@ -111,9 +109,10 @@ static void clear_uie(struct rtc_device *rtc) flush_scheduled_work(); spin_lock_irq(&rtc->irq_lock); } - rtc->irq_active = 0; + rtc->uie_irq_active = 0; } spin_unlock_irq(&rtc->irq_lock); + return 0; } static int set_uie(struct rtc_device *rtc) @@ -121,12 +120,12 @@ static int set_uie(struct rtc_device *rtc) struct rtc_time tm; int err; - err = rtc_read_time(&rtc->class_dev, &tm); + err = rtc_read_time(rtc, &tm); if (err) return err; spin_lock_irq(&rtc->irq_lock); - if (!rtc->irq_active) { - rtc->irq_active = 1; + if (!rtc->uie_irq_active) { + rtc->uie_irq_active = 1; rtc->stop_uie_polling = 0; rtc->oldsecs = tm.tm_sec; rtc->uie_task_active = 1; @@ -137,12 +136,22 @@ static int set_uie(struct rtc_device *rtc) spin_unlock_irq(&rtc->irq_lock); return 0; } + +int rtc_dev_update_irq_enable_emul(struct rtc_device *rtc, unsigned int enabled) +{ + if (enabled) + return set_uie(rtc); + else + return clear_uie(rtc); +} +EXPORT_SYMBOL(rtc_dev_update_irq_enable_emul); + #endif /* CONFIG_RTC_INTF_DEV_UIE_EMUL */ static ssize_t rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { - struct rtc_device *rtc = to_rtc_device(file->private_data); + struct rtc_device *rtc = file->private_data; DECLARE_WAITQUEUE(wait, current); unsigned long data; @@ -180,7 +189,7 @@ rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) if (ret == 0) { /* Check for any data updates */ if (rtc->ops->read_callback) - data = rtc->ops->read_callback(rtc->class_dev.dev, + data = rtc->ops->read_callback(rtc->dev.parent, data); if (sizeof(int) != sizeof(long) && @@ -196,7 +205,7 @@ rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) static unsigned int rtc_dev_poll(struct file *file, poll_table *wait) { - struct rtc_device *rtc = to_rtc_device(file->private_data); + struct rtc_device *rtc = file->private_data; unsigned long data; poll_wait(file, &rtc->irq_queue, wait); @@ -206,17 +215,20 @@ static unsigned int rtc_dev_poll(struct file *file, poll_table *wait) return (data != 0) ? (POLLIN | POLLRDNORM) : 0; } -static int rtc_dev_ioctl(struct inode *inode, struct file *file, +static long rtc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; - struct class_device *class_dev = file->private_data; - struct rtc_device *rtc = to_rtc_device(class_dev); + struct rtc_device *rtc = file->private_data; const struct rtc_class_ops *ops = rtc->ops; struct rtc_time tm; struct rtc_wkalrm alarm; void __user *uarg = (void __user *) arg; + err = mutex_lock_interruptible(&rtc->ops_lock); + if (err) + return err; + /* check that the calling task has appropriate permissions * for certain ioctls. doing this check here is useful * to avoid duplicate code in each driver. @@ -225,171 +237,220 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, case RTC_EPOCH_SET: case RTC_SET_TIME: if (!capable(CAP_SYS_TIME)) - return -EACCES; + err = -EACCES; break; case RTC_IRQP_SET: if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE)) - return -EACCES; + err = -EACCES; break; case RTC_PIE_ON: - if (!capable(CAP_SYS_RESOURCE)) - return -EACCES; + if (rtc->irq_freq > rtc->max_user_freq && + !capable(CAP_SYS_RESOURCE)) + err = -EACCES; break; } - /* avoid conflicting IRQ users */ - if (cmd == RTC_PIE_ON || cmd == RTC_PIE_OFF || cmd == RTC_IRQP_SET) { - spin_lock_irq(&rtc->irq_task_lock); - if (rtc->irq_task) - err = -EBUSY; - spin_unlock_irq(&rtc->irq_task_lock); - - if (err < 0) - return err; - } - - /* try the driver's ioctl interface */ - if (ops->ioctl) { - err = ops->ioctl(class_dev->dev, cmd, arg); - if (err != -ENOIOCTLCMD) - return err; - } - - /* if the driver does not provide the ioctl interface - * or if that particular ioctl was not implemented - * (-ENOIOCTLCMD), we will try to emulate here. + if (err) + goto done; + + /* + * Drivers *SHOULD NOT* provide ioctl implementations + * for these requests. Instead, provide methods to + * support the following code, so that the RTC's main + * features are accessible without using ioctls. + * + * RTC and alarm times will be in UTC, by preference, + * but dual-booting with MS-Windows implies RTCs must + * use the local wall clock time. */ switch (cmd) { case RTC_ALM_READ: - err = rtc_read_alarm(class_dev, &alarm); + mutex_unlock(&rtc->ops_lock); + + err = rtc_read_alarm(rtc, &alarm); if (err < 0) return err; if (copy_to_user(uarg, &alarm.time, sizeof(tm))) - return -EFAULT; - break; + err = -EFAULT; + return err; case RTC_ALM_SET: + mutex_unlock(&rtc->ops_lock); + if (copy_from_user(&alarm.time, uarg, sizeof(tm))) return -EFAULT; alarm.enabled = 0; alarm.pending = 0; - alarm.time.tm_mday = -1; - alarm.time.tm_mon = -1; - alarm.time.tm_year = -1; alarm.time.tm_wday = -1; alarm.time.tm_yday = -1; alarm.time.tm_isdst = -1; - err = rtc_set_alarm(class_dev, &alarm); - break; + + /* RTC_ALM_SET alarms may be up to 24 hours in the future. + * Rather than expecting every RTC to implement "don't care" + * for day/month/year fields, just force the alarm to have + * the right values for those fields. + * + * RTC_WKALM_SET should be used instead. Not only does it + * eliminate the need for a separate RTC_AIE_ON call, it + * doesn't have the "alarm 23:59:59 in the future" race. + * + * NOTE: some legacy code may have used invalid fields as + * wildcards, exposing hardware "periodic alarm" capabilities. + * Not supported here. + */ + { + unsigned long now, then; + + err = rtc_read_time(rtc, &tm); + if (err < 0) + return err; + rtc_tm_to_time(&tm, &now); + + alarm.time.tm_mday = tm.tm_mday; + alarm.time.tm_mon = tm.tm_mon; + alarm.time.tm_year = tm.tm_year; + err = rtc_valid_tm(&alarm.time); + if (err < 0) + return err; + rtc_tm_to_time(&alarm.time, &then); + + /* alarm may need to wrap into tomorrow */ + if (then < now) { + rtc_time_to_tm(now + 24 * 60 * 60, &tm); + alarm.time.tm_mday = tm.tm_mday; + alarm.time.tm_mon = tm.tm_mon; + alarm.time.tm_year = tm.tm_year; + } + } + + return rtc_set_alarm(rtc, &alarm); case RTC_RD_TIME: - err = rtc_read_time(class_dev, &tm); + mutex_unlock(&rtc->ops_lock); + + err = rtc_read_time(rtc, &tm); if (err < 0) return err; if (copy_to_user(uarg, &tm, sizeof(tm))) - return -EFAULT; - break; + err = -EFAULT; + return err; case RTC_SET_TIME: + mutex_unlock(&rtc->ops_lock); + if (copy_from_user(&tm, uarg, sizeof(tm))) return -EFAULT; - err = rtc_set_time(class_dev, &tm); + return rtc_set_time(rtc, &tm); + + case RTC_PIE_ON: + err = rtc_irq_set_state(rtc, NULL, 1); break; - case RTC_IRQP_READ: - if (ops->irq_set_freq) - err = put_user(rtc->irq_freq, (unsigned long *) arg); + case RTC_PIE_OFF: + err = rtc_irq_set_state(rtc, NULL, 0); break; + case RTC_AIE_ON: + mutex_unlock(&rtc->ops_lock); + return rtc_alarm_irq_enable(rtc, 1); + + case RTC_AIE_OFF: + mutex_unlock(&rtc->ops_lock); + return rtc_alarm_irq_enable(rtc, 0); + + case RTC_UIE_ON: + mutex_unlock(&rtc->ops_lock); + return rtc_update_irq_enable(rtc, 1); + + case RTC_UIE_OFF: + mutex_unlock(&rtc->ops_lock); + return rtc_update_irq_enable(rtc, 0); + case RTC_IRQP_SET: - if (ops->irq_set_freq) - err = rtc_irq_set_freq(class_dev, rtc->irq_task, arg); + err = rtc_irq_set_freq(rtc, NULL, arg); break; -#if 0 - case RTC_EPOCH_SET: -#ifndef rtc_epoch - /* - * There were no RTC clocks before 1900. - */ - if (arg < 1900) { - err = -EINVAL; - break; - } - rtc_epoch = arg; - err = 0; -#endif + case RTC_IRQP_READ: + err = put_user(rtc->irq_freq, (unsigned long __user *)uarg); break; - case RTC_EPOCH_READ: - err = put_user(rtc_epoch, (unsigned long __user *)uarg); - break; -#endif case RTC_WKALM_SET: + mutex_unlock(&rtc->ops_lock); if (copy_from_user(&alarm, uarg, sizeof(alarm))) return -EFAULT; - err = rtc_set_alarm(class_dev, &alarm); - break; + return rtc_set_alarm(rtc, &alarm); case RTC_WKALM_RD: - err = rtc_read_alarm(class_dev, &alarm); + mutex_unlock(&rtc->ops_lock); + err = rtc_read_alarm(rtc, &alarm); if (err < 0) return err; if (copy_to_user(uarg, &alarm, sizeof(alarm))) - return -EFAULT; - break; - -#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL - case RTC_UIE_OFF: - clear_uie(rtc); - return 0; + err = -EFAULT; + return err; - case RTC_UIE_ON: - return set_uie(rtc); -#endif default: - err = -ENOTTY; + /* Finally try the driver's ioctl interface */ + if (ops->ioctl) { + err = ops->ioctl(rtc->dev.parent, cmd, arg); + if (err == -ENOIOCTLCMD) + err = -ENOTTY; + } else + err = -ENOTTY; break; } +done: + mutex_unlock(&rtc->ops_lock); return err; } +static int rtc_dev_fasync(int fd, struct file *file, int on) +{ + struct rtc_device *rtc = file->private_data; + return fasync_helper(fd, file, on, &rtc->async_queue); +} + static int rtc_dev_release(struct inode *inode, struct file *file) { - struct rtc_device *rtc = to_rtc_device(file->private_data); + struct rtc_device *rtc = file->private_data; + + /* We shut down the repeating IRQs that userspace enabled, + * since nothing is listening to them. + * - Update (UIE) ... currently only managed through ioctls + * - Periodic (PIE) ... also used through rtc_*() interface calls + * + * Leave the alarm alone; it may be set to trigger a system wakeup + * later, or be used by kernel code, and is a one-shot event anyway. + */ + + /* Keep ioctl until all drivers are converted */ + rtc_dev_ioctl(file, RTC_UIE_OFF, 0); + rtc_update_irq_enable(rtc, 0); + rtc_irq_set_state(rtc, NULL, 0); -#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL - clear_uie(rtc); -#endif if (rtc->ops->release) - rtc->ops->release(rtc->class_dev.dev); + rtc->ops->release(rtc->dev.parent); - mutex_unlock(&rtc->char_lock); + clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags); return 0; } -static int rtc_dev_fasync(int fd, struct file *file, int on) -{ - struct rtc_device *rtc = to_rtc_device(file->private_data); - return fasync_helper(fd, file, on, &rtc->async_queue); -} - -static struct file_operations rtc_dev_fops = { +static const struct file_operations rtc_dev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = rtc_dev_read, .poll = rtc_dev_poll, - .ioctl = rtc_dev_ioctl, + .unlocked_ioctl = rtc_dev_ioctl, .open = rtc_dev_open, .release = rtc_dev_release, .fasync = rtc_dev_fasync, @@ -397,20 +458,18 @@ static struct file_operations rtc_dev_fops = { /* insertion/removal hooks */ -static int rtc_dev_add_device(struct class_device *class_dev, - struct class_interface *class_intf) +void rtc_dev_prepare(struct rtc_device *rtc) { - int err = 0; - struct rtc_device *rtc = to_rtc_device(class_dev); + if (!rtc_devt) + return; if (rtc->id >= RTC_DEV_MAX) { - dev_err(class_dev->dev, "too many RTCs\n"); - return -EINVAL; + dev_dbg(&rtc->dev, "%s: too many RTC devices\n", rtc->name); + return; } - mutex_init(&rtc->char_lock); - spin_lock_init(&rtc->irq_lock); - init_waitqueue_head(&rtc->irq_queue); + rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); + #ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL INIT_WORK(&rtc->uie_task, rtc_uie_task); setup_timer(&rtc->uie_timer, rtc_uie_timer, (unsigned long)rtc); @@ -418,100 +477,35 @@ static int rtc_dev_add_device(struct class_device *class_dev, cdev_init(&rtc->char_dev, &rtc_dev_fops); rtc->char_dev.owner = rtc->owner; +} - if (cdev_add(&rtc->char_dev, MKDEV(MAJOR(rtc_devt), rtc->id), 1)) { - dev_err(class_dev->dev, - "failed to add char device %d:%d\n", +void rtc_dev_add_device(struct rtc_device *rtc) +{ + if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1)) + dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n", + rtc->name, MAJOR(rtc_devt), rtc->id); + else + dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name, MAJOR(rtc_devt), rtc->id); - return -ENODEV; - } - - rtc->rtc_dev = class_device_create(rtc_dev_class, NULL, - MKDEV(MAJOR(rtc_devt), rtc->id), - class_dev->dev, "rtc%d", rtc->id); - if (IS_ERR(rtc->rtc_dev)) { - dev_err(class_dev->dev, "cannot create rtc_dev device\n"); - err = PTR_ERR(rtc->rtc_dev); - goto err_cdev_del; - } - - dev_info(class_dev->dev, "rtc intf: dev (%d:%d)\n", - MAJOR(rtc->rtc_dev->devt), - MINOR(rtc->rtc_dev->devt)); - - return 0; - -err_cdev_del: - - cdev_del(&rtc->char_dev); - return err; } -static void rtc_dev_remove_device(struct class_device *class_dev, - struct class_interface *class_intf) +void rtc_dev_del_device(struct rtc_device *rtc) { - struct rtc_device *rtc = to_rtc_device(class_dev); - - if (rtc->rtc_dev) { - dev_dbg(class_dev->dev, "removing char %d:%d\n", - MAJOR(rtc->rtc_dev->devt), - MINOR(rtc->rtc_dev->devt)); - - class_device_unregister(rtc->rtc_dev); + if (rtc->dev.devt) cdev_del(&rtc->char_dev); - } } -/* interface registration */ - -static struct class_interface rtc_dev_interface = { - .add = &rtc_dev_add_device, - .remove = &rtc_dev_remove_device, -}; - -static int __init rtc_dev_init(void) +void __init rtc_dev_init(void) { int err; - rtc_dev_class = class_create(THIS_MODULE, "rtc-dev"); - if (IS_ERR(rtc_dev_class)) - return PTR_ERR(rtc_dev_class); - err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc"); - if (err < 0) { - printk(KERN_ERR "%s: failed to allocate char dev region\n", - __FILE__); - goto err_destroy_class; - } - - err = rtc_interface_register(&rtc_dev_interface); - if (err < 0) { - printk(KERN_ERR "%s: failed to register the interface\n", - __FILE__); - goto err_unregister_chrdev; - } - - return 0; - -err_unregister_chrdev: - unregister_chrdev_region(rtc_devt, RTC_DEV_MAX); - -err_destroy_class: - class_destroy(rtc_dev_class); - - return err; + if (err < 0) + pr_err("failed to allocate char dev region\n"); } -static void __exit rtc_dev_exit(void) +void __exit rtc_dev_exit(void) { - class_interface_unregister(&rtc_dev_interface); - class_destroy(rtc_dev_class); - unregister_chrdev_region(rtc_devt, RTC_DEV_MAX); + if (rtc_devt) + unregister_chrdev_region(rtc_devt, RTC_DEV_MAX); } - -subsys_initcall(rtc_dev_init); -module_exit(rtc_dev_exit); - -MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); -MODULE_DESCRIPTION("RTC class dev interface"); -MODULE_LICENSE("GPL"); |
