X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=drivers%2Frtc%2Frtc-dev.c;h=f4e5f0040ff7a43263a2af50b9eb694624f3fa91;hb=f8245c26886c912627ebc49f714e4491261224c4;hp=6c9ad92747fddd2c35061c5e49a4bcc1ad04e2cd;hpb=3418ff76119da52f808eb496191d1fd380f53f3d;p=safe%2Fjmp%2Flinux-2.6 diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c index 6c9ad92..f4e5f00 100644 --- a/drivers/rtc/rtc-dev.c +++ b/drivers/rtc/rtc-dev.c @@ -13,8 +13,8 @@ #include #include +#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... */ @@ -24,7 +24,7 @@ static int rtc_dev_open(struct inode *inode, struct file *file) int err; struct rtc_device *rtc = container_of(inode->i_cdev, struct rtc_device, char_dev); - struct rtc_class_ops *ops = rtc->ops; + 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 @@ -32,9 +32,9 @@ static int rtc_dev_open(struct inode *inode, struct file *file) if (!(mutex_trylock(&rtc->char_lock))) 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; @@ -48,6 +48,96 @@ static int rtc_dev_open(struct inode *inode, struct file *file) return err; } +#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL +/* + * Routine to poll RTC seconds field for change as often as possible, + * after first RTC_UIE use timer to reduce polling + */ +static void rtc_uie_task(struct work_struct *work) +{ + struct rtc_device *rtc = + container_of(work, struct rtc_device, uie_task); + struct rtc_time tm; + int num = 0; + int err; + + err = rtc_read_time(rtc, &tm); + + local_irq_disable(); + spin_lock(&rtc->irq_lock); + if (rtc->stop_uie_polling || err) { + rtc->uie_task_active = 0; + } else if (rtc->oldsecs != tm.tm_sec) { + num = (tm.tm_sec + 60 - rtc->oldsecs) % 60; + rtc->oldsecs = tm.tm_sec; + rtc->uie_timer.expires = jiffies + HZ - (HZ/10); + rtc->uie_timer_active = 1; + rtc->uie_task_active = 0; + add_timer(&rtc->uie_timer); + } else if (schedule_work(&rtc->uie_task) == 0) { + rtc->uie_task_active = 0; + } + spin_unlock(&rtc->irq_lock); + if (num) + rtc_update_irq(rtc, num, RTC_UF | RTC_IRQF); + local_irq_enable(); +} +static void rtc_uie_timer(unsigned long data) +{ + struct rtc_device *rtc = (struct rtc_device *)data; + unsigned long flags; + + spin_lock_irqsave(&rtc->irq_lock, flags); + rtc->uie_timer_active = 0; + rtc->uie_task_active = 1; + if ((schedule_work(&rtc->uie_task) == 0)) + rtc->uie_task_active = 0; + spin_unlock_irqrestore(&rtc->irq_lock, flags); +} + +static void clear_uie(struct rtc_device *rtc) +{ + spin_lock_irq(&rtc->irq_lock); + if (rtc->irq_active) { + rtc->stop_uie_polling = 1; + if (rtc->uie_timer_active) { + spin_unlock_irq(&rtc->irq_lock); + del_timer_sync(&rtc->uie_timer); + spin_lock_irq(&rtc->irq_lock); + rtc->uie_timer_active = 0; + } + if (rtc->uie_task_active) { + spin_unlock_irq(&rtc->irq_lock); + flush_scheduled_work(); + spin_lock_irq(&rtc->irq_lock); + } + rtc->irq_active = 0; + } + spin_unlock_irq(&rtc->irq_lock); +} + +static int set_uie(struct rtc_device *rtc) +{ + struct rtc_time tm; + int err; + + err = rtc_read_time(rtc, &tm); + if (err) + return err; + spin_lock_irq(&rtc->irq_lock); + if (!rtc->irq_active) { + rtc->irq_active = 1; + rtc->stop_uie_polling = 0; + rtc->oldsecs = tm.tm_sec; + rtc->uie_task_active = 1; + if (schedule_work(&rtc->uie_task) == 0) + rtc->uie_task_active = 0; + } + rtc->irq_data = 0; + spin_unlock_irq(&rtc->irq_lock); + return 0; +} +#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) @@ -90,7 +180,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) && @@ -120,19 +210,40 @@ static int rtc_dev_ioctl(struct inode *inode, 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_class_ops *ops = rtc->ops; + 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; + /* check that the calling task has appropriate permissions + * for certain ioctls. doing this check here is useful + * to avoid duplicate code in each driver. + */ + switch (cmd) { + case RTC_EPOCH_SET: + case RTC_SET_TIME: + if (!capable(CAP_SYS_TIME)) + return -EACCES; + break; + + case RTC_IRQP_SET: + if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE)) + return -EACCES; + break; + + case RTC_PIE_ON: + if (!capable(CAP_SYS_RESOURCE)) + return -EACCES; + break; + } + /* avoid conflicting IRQ users */ if (cmd == RTC_PIE_ON || cmd == RTC_PIE_OFF || cmd == RTC_IRQP_SET) { - spin_lock(&rtc->irq_task_lock); + spin_lock_irq(&rtc->irq_task_lock); if (rtc->irq_task) err = -EBUSY; - spin_unlock(&rtc->irq_task_lock); + spin_unlock_irq(&rtc->irq_task_lock); if (err < 0) return err; @@ -140,19 +251,19 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, /* try the driver's ioctl interface */ if (ops->ioctl) { - err = ops->ioctl(class_dev->dev, cmd, arg); - if (err != -EINVAL) + err = ops->ioctl(rtc->dev.parent, cmd, arg); + if (err != -ENOIOCTLCMD) return err; } /* if the driver does not provide the ioctl interface * or if that particular ioctl was not implemented - * (-EINVAL), we will try to emulate here. + * (-ENOIOCTLCMD), we will try to emulate here. */ switch (cmd) { case RTC_ALM_READ: - err = rtc_read_alarm(class_dev, &alarm); + err = rtc_read_alarm(rtc, &alarm); if (err < 0) return err; @@ -166,17 +277,53 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, 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); + + /* 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; + } + } + + err = rtc_set_alarm(rtc, &alarm); break; case RTC_RD_TIME: - err = rtc_read_time(class_dev, &tm); + err = rtc_read_time(rtc, &tm); if (err < 0) return err; @@ -185,14 +332,22 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, break; case RTC_SET_TIME: - if (!capable(CAP_SYS_TIME)) - return -EACCES; - if (copy_from_user(&tm, uarg, sizeof(tm))) return -EFAULT; - err = rtc_set_time(class_dev, &tm); + err = rtc_set_time(rtc, &tm); break; + + case RTC_IRQP_READ: + if (ops->irq_set_freq) + err = put_user(rtc->irq_freq, (unsigned long __user *)uarg); + break; + + case RTC_IRQP_SET: + if (ops->irq_set_freq) + err = rtc_irq_set_freq(rtc, rtc->irq_task, arg); + break; + #if 0 case RTC_EPOCH_SET: #ifndef rtc_epoch @@ -203,10 +358,6 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, err = -EINVAL; break; } - if (!capable(CAP_SYS_TIME)) { - err = -EACCES; - break; - } rtc_epoch = arg; err = 0; #endif @@ -220,11 +371,11 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, if (copy_from_user(&alarm, uarg, sizeof(alarm))) return -EFAULT; - err = rtc_set_alarm(class_dev, &alarm); + err = rtc_set_alarm(rtc, &alarm); break; case RTC_WKALM_RD: - err = rtc_read_alarm(class_dev, &alarm); + err = rtc_read_alarm(rtc, &alarm); if (err < 0) return err; @@ -232,8 +383,16 @@ static int rtc_dev_ioctl(struct inode *inode, struct file *file, return -EFAULT; break; +#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL + case RTC_UIE_OFF: + clear_uie(rtc); + return 0; + + case RTC_UIE_ON: + return set_uie(rtc); +#endif default: - err = -EINVAL; + err = -ENOTTY; break; } @@ -244,8 +403,11 @@ static int rtc_dev_release(struct inode *inode, struct file *file) { struct rtc_device *rtc = to_rtc_device(file->private_data); +#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); return 0; @@ -257,7 +419,7 @@ static int rtc_dev_fasync(int fd, struct file *file, int on) 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, @@ -270,118 +432,58 @@ 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; + pr_debug("%s: too many RTC devices\n", rtc->name); + return; } + rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); + mutex_init(&rtc->char_lock); spin_lock_init(&rtc->irq_lock); init_waitqueue_head(&rtc->irq_queue); +#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); +#endif 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)) { - cdev_del(&rtc->char_dev); - 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)) + printk(KERN_WARNING "%s: failed to add char device %d:%d\n", + rtc->name, MAJOR(rtc_devt), rtc->id); + else + pr_debug("%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) { + 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; } -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); } - -module_init(rtc_dev_init); -module_exit(rtc_dev_exit); - -MODULE_AUTHOR("Alessandro Zummo "); -MODULE_DESCRIPTION("RTC class dev interface"); -MODULE_LICENSE("GPL");