首页 > 系统服务 > 详细

Linux RTC驱动分析及应用

时间:2020-12-15 00:10:10      阅读:37      评论:0      收藏:0      [点我收藏+]

一、RTC简介

RTC(real-time clock)简称实时时钟,主要是用来计时,产生闹钟等。RTC一般有个备份电池,所以即使设备关机掉电,RTC也能在备份电池的供电下继续正常计时,这样在每次系统开机上电时就可以从RTC设备中读取到准确的时间。RTC时间在每次系统启动的时候会使用,在需要的时候也可以由系统将要设置的时间写入到RTC设备。Linux系统中,RTC可以使用周期性的中断来产生闹钟,也可以在系统suspend的时候作为系统的唤醒源使用。内核为RTC设备设计了一套驱动模型,如果驱动工程师想增加某一种新RTC硬件的驱动,只需要去编写芯片相关的代码,然后调用内核提供函数注册到RTC核心层即可。

二、RTC设备描述

在介绍内核RTC驱动结构之前,先来看RTC设备在内核中是如何描述的

rtc_device结构体

struct rtc_device
{
    struct device dev;                      //代表是一个设备
    struct module *owner;

    int id;                                 //rtc设备编号
    char name[RTC_DEVICE_NAME_SIZE];        //rtc设备的名称

    const struct rtc_class_ops *ops;        //rtc操作函数集,由驱动程序实现
    struct mutex ops_lock;                  //操作函数集的互斥锁

    struct cdev char_dev;                   //cdev结构体,代表rtc是字符设备
    unsigned long flags;                    //rtc的状态标志,例如RTC_DEV_BUSY

    unsigned long irq_data;                 //rtc中断数据
    spinlock_t irq_lock;                    //互斥访问数据
    wait_queue_head_t irq_queue;            //数据查询中用到的rtc队列
    struct fasync_struct *async_queue;      //异步队列

    struct rtc_task *irq_task;              //在中断中使用task传输数据
    spinlock_t irq_task_lock;               //task传输互斥
    int irq_freq;                           //rtc的中断频率
    int max_user_freq;                      //rtc的最大中断频率

    struct timerqueue_head timerqueue;      //定时器队列
    struct rtc_timer aie_timer;             //报警中断定时器
    struct rtc_timer uie_rtctimer;          //更新中断定时器
    struct hrtimer pie_timer;               //周期中断高精度定时器
    int pie_enabled;                        //周期中断使能标志
    struct work_struct irqwork;
    /* Some hardware can‘t support UIE mode */
    int uie_unsupported;
    ...
};

rtc设备在内核中是以字符设备形式存在的,内核中使用rtc_device结构体来抽象一个rtc设备。rtc_device结构体屏蔽了不同rtc硬件之间的差异,通过rtc_class_ops结构体为上层提供了访问硬件设备的统一接口,该结构体中包含了对硬件操作的相关函数。

struct rtc_class_ops {
    int (*open)(struct device *);
    void (*release)(struct device *);
    int (*ioctl)(struct device *, unsigned int, unsigned long);
    int (*read_time)(struct device *, struct rtc_time *);
    int (*set_time)(struct device *, struct rtc_time *);
    int (*read_alarm)(struct device *, struct rtc_wkalrm *);
    int (*set_alarm)(struct device *, struct rtc_wkalrm *);
    int (*proc)(struct device *, struct seq_file *);
    int (*set_mmss)(struct device *, unsigned long secs);
    int (*read_callback)(struct device *, int data);
    int (*alarm_irq_enable)(struct device *, unsigned int enabled);
};

rtc_class_ops结构体中的操作函数大部分需要驱动程序实现,比如open, read_time, set_time等。这些函数大多数都是和rtc芯片的操作有关,上层通过这些函数进行对硬件设备的操作,比如调用set_time设置时间、read_time获取时间等等。

三、内核源代码分析

好了,有了上面的知识作为铺垫,现在我们开始来从源码角度去详细分析内核中rtc驱动程序的框架。这里以s3c24xx芯片为例(s3c24xx芯片自带RTC外设),一步步推出内核中rtc设备驱动是如何实现的。

rtc设备驱动程序存放在内核源码树的drivers/rtc目录,s3c24xx芯片的RTC驱动程序对应的是rtc-s3c.c文件。下面我们就从这个文件中的入口函数进行分析

入口函数

module_platform_driver(s3c_rtc_driver);
static struct platform_driver s3c_rtc_driver = {
    .probe        = s3c_rtc_probe,
    .remove        = __devexit_p(s3c_rtc_remove),
    .suspend    = s3c_rtc_suspend,
    .resume        = s3c_rtc_resume,
    .id_table    = s3c_rtc_driver_ids,
    .driver        = {
        .name    = "s3c-rtc",
        .owner    = THIS_MODULE,
        .of_match_table    = s3c_rtc_dt_match,
    },
};

modules_platform_driver是一个宏,这个宏在include\linux\device.h文件中定义,内容如下:

#define module_platform_driver(__platform_driver) \
    module_driver(__platform_driver, platform_driver_register,             platform_driver_unregister)
            
#define module_driver(__driver, __register, __unregister, ...) static int __init __driver##_init(void) {     return __register(&(__driver) , ##__VA_ARGS__); } module_init(__driver##_init); static void __exit __driver##_exit(void) {     __unregister(&(__driver) , ##__VA_ARGS__); } module_exit(__driver##_exit);

根据上述宏定义,将module_platform_device(s3c_rtc_driver)展开,展开内容如下所示:

static int __init s3c_rtc_driver_init(void) 
{ 
    return platform_driver_register(&(s3c_rtc_driver)); 
} 
module_init(s3c_rtc_driver_init); 
static void __exit s3c_rtc_driver_exit(void) 
{ 
    platform_driver_unregister(&(s3c_rtc_driver)); 
} 
module_exit(s3c_rtc_driver_exit);

通过上面的展开操作,我们可以看出module_platform_driver(s3c_rtc_driver)即实现了注册、卸载一个platform驱动程序的入口和出口函数。

我们知道platform驱动在注册时,会查找platform总线上所有的挂载的platform设备,并调用platform总线的match函数进行比较,如果匹配成功将调用platform驱动的probe函数。

下面来看s3c_rtc_driver的probe函数s3c_rtc_probe

static int __devinit s3c_rtc_probe(struct platform_device *pdev)
{
    ...
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    ...
    s3c_rtc_mem = request_mem_region(res->start, resource_size(res),
                     pdev->name);
    ...
    s3c_rtc_base = ioremap(res->start, resource_size(res));
    ...
    rtc_clk = clk_get(&pdev->dev, "rtc");
    ...
    clk_enable(rtc_clk);

    s3c_rtc_enable(pdev, 1);
    ...
    device_init_wakeup(&pdev->dev, 1);

    rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
                  THIS_MODULE);
    ...
}

s3c_rtc_probe函数实现的基本上是硬件相关的操作,首先获取与之匹配的platform_device中描述的硬件相关的资源,如s3c24xx芯片RTC外设寄存器地址,完成寄存器物理地址到虚拟地址映射后,使能RTC外设时钟、初始化RTC外设寄存器,然后调用rtc_device_register函数向内核注册一个rtc设备。

rtc_device_register函数是驱动程序找到匹配的设备后,向内核注册一个rtc_device,来详细分析这个函数

struct rtc_device *rtc_device_register(const char *name, struct device *dev,
                    const struct rtc_class_ops *ops,
                    struct module *owner)
{
    struct rtc_device *rtc;
    struct rtc_wkalrm alrm;
    int id, err;

    id = ida_simple_get(&rtc_ida, 0, 0, GFP_KERNEL); ------------------------->①
    ...

    rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);--------------------->②
    ...

    rtc->id = id;
    rtc->ops = ops;
    ...

    mutex_init(&rtc->ops_lock);
    spin_lock_init(&rtc->irq_lock);
    spin_lock_init(&rtc->irq_task_lock);
    init_waitqueue_head(&rtc->irq_queue);

    /* Init timerqueue */
    timerqueue_init_head(&rtc->timerqueue);
    INIT_WORK(&rtc->irqwork, rtc_timer_do_work);
    ...

    /* Check to see if there is an ALARM already set in hw */
    err = __rtc_read_alarm(rtc, &alrm);
    ...

    strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
    dev_set_name(&rtc->dev, "rtc%d", id);

    rtc_dev_prepare(rtc);------------------------------------------------>③

    err = device_register(&rtc->dev);
    ...
    rtc_dev_add_device(rtc);-------------------------------------------->④
    ...
}

① 获取一个新的设备id

② 分配一个rtc_device结构体,初始化rtc_device结构体中的成员变量,比如使用到的锁,初始化定时器等等

rtc_device结构体中的rtc设备操作函数指针rtc_class_ops指针指向s3c_rtcops,这部分内容在后面再进行展开分析

③ 调用rtc_dev_prepare初始化字符设备,

void rtc_dev_prepare(struct rtc_device *rtc)
{
    ...
    rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
    ...
    cdev_init(&rtc->char_dev, &rtc_dev_fops);
    rtc->char_dev.owner = rtc->owner;
}

④ 调用ret_dev_add_device添加一个字符设备

void rtc_dev_add_device(struct rtc_device *rtc)
{
    if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))
    ...
}

rtc_device_register函数动态分配了一个rtc_device结构体,并初始化分配的rtc_device结构体成员。最后向内核注册了一个字符设备,注册的字符设备的file_operations结构体指针指向rtc_dev_fops结构体。当系统访问rtc设备时,对rtc设备的操作会作用到字符设备的file_operations结构体的成员函数。

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,
};

以open函数进行分析,rtc_dev_open

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);
    const struct rtc_class_ops *ops = rtc->ops;

    ...

    file->private_data = rtc;

    err = ops->open ? ops->open(rtc->dev.parent) : 0;
    ...
}

rtc_dev_open函数在打开rtc设备节点时会被调用,首先根据inode->i_cdev使用container_of获取对应的rtc_device结构体(inode->i_cdev = &rtc->char_dev)。

获取rtc_device结构体后,找到他成员rtc_class_ops结构体指针,调用rtc_class_ops结构体指针指向对象的open函数。

由此可见,访问设备的file_operations函数最终会调用到对应的rtc_device结构体中对rtc硬件进行操作的函数。

前面讲过,rtc_device结构体中的rtc设备操作函数指针rtc_class_ops指针指向s3c_rtcops,我们来看下s3c_rtcops结构体内容

static const struct rtc_class_ops s3c_rtcops = {
    .read_time    = s3c_rtc_gettime,
    .set_time    = s3c_rtc_settime,
    .read_alarm    = s3c_rtc_getalarm,
    .set_alarm    = s3c_rtc_setalarm,
    .proc        = s3c_rtc_proc,
    .alarm_irq_enable = s3c_rtc_setaie,
};

s3c_rtcops结构体中实现了.read_time、.set_time等成员函数,这些成员函数在系统调用使用ioctl读取时间或设置时间时被调用,我们进一步分析看它是如何返回时间信息的

 

static long rtc_dev_ioctl(struct file *file,
        unsigned int cmd, unsigned long arg)
{
    
    struct rtc_device *rtc = file->private_data;
    const struct rtc_class_ops *ops = rtc->ops;
    struct rtc_time tm;
    ...
    switch (cmd) {
    ...

    case RTC_RD_TIME:
        mutex_unlock(&rtc->ops_lock);

        err = rtc_read_time(rtc, &tm);
                |-->__rtc_read_time(rtc, tm);
                    |-->memset(tm, 0, sizeof(struct rtc_time));
                    |-->err = rtc->ops->read_time(rtc->dev.parent, tm);    
                    
        if (copy_to_user(uarg, &tm, sizeof(tm)))
            err = -EFAULT;
        return err;

    ...
}

 

 

应用程序使用ioctl设备时间,最终会调用到rtc_dev_ioctl,执行rtc_read_time获取当前时间,将获取时间存放到rtc_time结构体中,最后使用copy_to_user拷贝到用户空间。

rtc_read_time函数最终会调用到s3c_rtcops.rtc_dev_ioctl,s3c_rtcops.rtc_dev_ioctl = s3c_rtc_gettime

s3c_rtc_gettime函数

 

static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
    ...
    rtc_tm->tm_min  = readb(base + S3C2410_RTCMIN);
    rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
    rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
    rtc_tm->tm_mon  = readb(base + S3C2410_RTCMON);
    rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
    rtc_tm->tm_sec  = readb(base + S3C2410_RTCSEC);
    ...
}

s3c_rtc_gettime函数读取S3C24XX芯片RTC外设保存当前时间寄存器值,将读取时间信息填充到一个rtc_time结构体中,这样便获取了当前时间信息。

由以上的分析,我们知道了

 

Linux RTC驱动分析及应用

原文:https://www.cnblogs.com/053179hu/p/14130300.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!