Chinaunix首页 | 论坛 | 博客
  • 博客访问: 35994
  • 博文数量: 8
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 12
  • 用 户 组: 普通用户
  • 注册时间: 2014-01-09 08:56
文章分类
文章存档

2014年(8)

我的朋友

分类: 嵌入式

2014-01-09 09:03:47

        在第一部分中,rtc_device_register函数调用了rtc-dev.c中的rtc_dev_prepare
void rtc_dev_prepare(struct rtc_device *rtc)

{
    if (!rtc_devt)
        return;
    if (rtc->id >= RTC_DEV_MAX) {
        pr_debug("%s: too many RTC devices\n", rtc->name);
        return;
    }
    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);
#endif
    cdev_init(&rtc->char_dev, &rtc_dev_fops);
    rtc->char_dev.owner = rtc->owner;
}
static const struct file_operations rtc_dev_fops = {
    .owner        = THIS_MODULE,
    .llseek        = no_llseek,
    .read        = rtc_dev_read,
    .poll        = rtc_dev_poll,
    .unlocked_ioctl    = rtc_dev_ioctl,
    .open        = rtc_dev_open,
    .release    = rtc_dev_release,
    .fasync        = rtc_dev_fasync,
};
        RTC操作函数集,接下来一一讲述:
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);    //通过设备号找到结构体rtc
    const struct rtc_class_ops *ops = rtc->ops;
    if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))
        return -EBUSY;
    file->private_data = rtc;    //赋给文件私有指针
    err = ops->open ? ops->open(rtc->dev.parent) : 0;    //ops中的open没有定义,对应一中的struct rtc_class_ops pcf8563_rtc_ops
    if (err == 0) {
        spin_lock_irq(&rtc->irq_lock);
        rtc->irq_data = 0;
        spin_unlock_irq(&rtc->irq_lock);
        return 0;
    }
    /* something has gone wrong */
    clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);
    return err;
}
static int rtc_dev_release(struct inode *inode, struct file *file)
{
    struct rtc_device *rtc = file->private_data;    //对应rtc_dev_open中的file->private_data = rtc;
    /* 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);
    if (rtc->ops->release)
        rtc->ops->release(rtc->dev.parent);
    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 = file->private_data;
    return fasync_helper(fd, file, on, &rtc->async_queue);
}
static unsigned int rtc_dev_poll(struct file *file, poll_table *wait)
{
    struct rtc_device *rtc = file->private_data;
    unsigned long data;
    poll_wait(file, &rtc->irq_queue, wait);
    data = rtc->irq_data;
    return (data != 0) ? (POLLIN | POLLRDNORM) : 0;
}
static ssize_t
rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    struct rtc_device *rtc = file->private_data;
    DECLARE_WAITQUEUE(wait, current);
    unsigned long data;
    ssize_t ret;
    if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
        return -EINVAL;
    add_wait_queue(&rtc->irq_queue, &wait);
    do {
        __set_current_state(TASK_INTERRUPTIBLE);
        spin_lock_irq(&rtc->irq_lock);
        data = rtc->irq_data;
        rtc->irq_data = 0;
        spin_unlock_irq(&rtc->irq_lock);
        if (data != 0) {
            ret = 0;
            break;
        }
        if (file->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            break;
        }
        if (signal_pending(current)) {
            ret = -ERESTARTSYS;
            break;
        }
        schedule();
    } while (1);
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&rtc->irq_queue, &wait);
    if (ret == 0) {
        /* Check for any data updates */
        if (rtc->ops->read_callback)
            data = rtc->ops->read_callback(rtc->dev.parent,
                               data);
        if (sizeof(int) != sizeof(long) &&
            count == sizeof(unsigned int))
            ret = put_user(data, (unsigned int __user *)buf) ?:
                sizeof(unsigned int);
        else
            ret = put_user(data, (unsigned long __user *)buf) ?:
                sizeof(unsigned long);
    }
    return ret;
}
        这里的read不是应用程序用来获取时间的,而是有其他的作用,他帮助应用程序周期性的完成一些工作。如果要使用这个功能,应用程序首先保证RTC驱动程序提供这样的功能。这个功能是这样实现的:进程读取/dev/rtc(n),进程睡眠直到RTC中断将他唤醒。我们可以发现,这里的睡眠 是ldd3中提到的手工睡眠。这个函数的手工休眠过程如下:首先调用DECLARE_WAITQUEUE(wait, current),声明一个等待队列入口,然后调用add_wait_queue将这个入口加入到RTC的irq等待队列里,然后进入循环。在循环里首先 把进程的状态改成TASK_INTERRUPTIBLE,这样进程就不能再被调度运行。但是现在进程还在运行,没有进入睡眠状态。程序然后读取RTC里面的irq_data,如果不是零,那么程序跳出这个循环,进程不会睡眠。因为这个irq_data在rtc的中断处理程序会被赋值,而读过之后就会清零, 所以如果数据不是零的话说明发生过一次中断。如果是零那么没有发生中断,调用schedule,进程会被调度出可运行队列,从而让出处理器,真正进入睡眠。跳出循环代表被唤醒,然后将进程状态改变为可运行,移除等待队列入口。最后将读回的数据传给用户空间。

static long rtc_dev_ioctl(struct file *file,
        unsigned int cmd, unsigned long arg)
{
    int err = 0;
    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.
     */
    switch (cmd) {
    case RTC_EPOCH_SET:
    case RTC_SET_TIME:
        if (!capable(CAP_SYS_TIME))
            err = -EACCES;
        break;
    case RTC_IRQP_SET:
        if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))
            err = -EACCES;
        break;
    case RTC_PIE_ON:
        if (rtc->irq_freq > rtc->max_user_freq &&
                !capable(CAP_SYS_RESOURCE))
            err = -EACCES;
        break;
    }
    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:
        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)))
            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_wday = -1;
        alarm.time.tm_yday = -1;
        alarm.time.tm_isdst = -1;
        /* 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:
        mutex_unlock(&rtc->ops_lock);
        err = rtc_read_time(rtc, &tm);    //读时间
        if (err < 0)
            return err;
        if (copy_to_user(uarg, &tm, sizeof(tm)))
            err = -EFAULT;
        return err;
    case RTC_SET_TIME:
        mutex_unlock(&rtc->ops_lock);
        if (copy_from_user(&tm, uarg, sizeof(tm)))
            return -EFAULT;
        return rtc_set_time(rtc, &tm);    //设置时间
    case RTC_PIE_ON:
        err = rtc_irq_set_state(rtc, NULL, 1);    //设置中断状态,第四部分讲述
        break;
    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:
        err = rtc_irq_set_freq(rtc, NULL, arg);    //设置中断频率,第四部分讲述
        break;
    case RTC_IRQP_READ:
        err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
        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
        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;
        return rtc_set_alarm(rtc, &alarm);
    case RTC_WKALM_RD:
        mutex_unlock(&rtc->ops_lock);
        err = rtc_read_alarm(rtc, &alarm);
        if (err < 0)
            return err;
        if (copy_to_user(uarg, &alarm, sizeof(alarm)))
            err = -EFAULT;
        return err;
    default:
        /* 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;
}

        rtc_dev_ioctl中调用的函数基本都在interface.c中。
        读闹钟:
int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
    int err;
    err = mutex_lock_interruptible(&rtc->ops_lock);
    if (err)
        return err;
    if (rtc->ops == NULL)
        err = -ENODEV;
    else if (!rtc->ops->read_alarm)
        err = -EINVAL;
    else {
        memset(alarm, 0, sizeof(struct rtc_wkalrm));
        alarm->enabled = rtc->aie_timer.enabled;
        alarm->time = rtc_ktime_to_tm(rtc->aie_timer.node.expires);
    }
    mutex_unlock(&rtc->ops_lock);
    return err;
}
EXPORT_SYMBOL_GPL(rtc_read_alarm);
        设置闹钟:
int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
    int err;
    err = rtc_valid_tm(&alarm->time);
    if (err != 0)
        return err;
    err = mutex_lock_interruptible(&rtc->ops_lock);
    if (err)
        return err;
    if (rtc->aie_timer.enabled) {
        rtc_timer_remove(rtc, &rtc->aie_timer);
    }
    rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);
    rtc->aie_timer.period = ktime_set(0, 0);
    if (alarm->enabled) {
        err = rtc_timer_enqueue(rtc, &rtc->aie_timer);
    }
    mutex_unlock(&rtc->ops_lock);
    return err;
}
EXPORT_SYMBOL_GPL(rtc_set_alarm);
        读时间:
int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
    int err;
    err = mutex_lock_interruptible(&rtc->ops_lock);
    if (err)
        return err;
    err = __rtc_read_time(rtc, tm);
    mutex_unlock(&rtc->ops_lock);
    return err;
}
EXPORT_SYMBOL_GPL(rtc_read_time);
static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
    int err;
    if (!rtc->ops)
        err = -ENODEV;
    else if (!rtc->ops->read_time)
        err = -EINVAL;
    else {
        memset(tm, 0, sizeof(struct rtc_time));
        err = rtc->ops->read_time(rtc->dev.parent, tm);
    }
    return err;
}
          这个函数用了一个信号来保证在同一时刻只有一个进程可以获取时间。锁定了这个信号量后,调用rtc->ops里面read函数,这个函数是由具体的驱动程序实现的,操作底层硬件。读回的时间存放在rtc_time结构里面的。
        设置时间:
int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
{
    int err;
    err = rtc_valid_tm(tm);
    if (err != 0)
        return err;
    err = mutex_lock_interruptible(&rtc->ops_lock);
    if (err)
        return err;
    if (!rtc->ops)
        err = -ENODEV;
    else if (rtc->ops->set_time)
        err = rtc->ops->set_time(rtc->dev.parent, tm);
    else if (rtc->ops->set_mmss) {
        unsigned long secs;
        err = rtc_tm_to_time(tm, &secs);
        if (err == 0)
            err = rtc->ops->set_mmss(rtc->dev.parent, secs);
    } else
        err = -EINVAL;
    mutex_unlock(&rtc->ops_lock);
    return err;
}
EXPORT_SYMBOL_GPL(rtc_set_time);
          这个函数其实和rtc_read_time函数差不多,同样是锁定信号量,同样是调用底层驱动函数。但是这里的设置时间提供了两个调用:一个是 set_time,一个是set_mmss。因为有的RTC硬件只计算秒数,不关心墙钟时间,所以如果是这样的RTC,必须实现set_mmss来设置时间。pcf8563只需要set_time即可。
          ioctl函数还涉及到闹钟和中断设置的程序,这些统一在第四部分讲述。
阅读(1192) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~