• 30.Linux-RTC驱动分析及使用


     linux中的rtc驱动位于drivers/rtc下,里面包含了许多开发平台的RTC驱动,我们这里是以S3C24xx为主,所以它的RTC驱动为rtc-s3c.c


    1.进入./drivers/rtc/rtc-s3c.c

    还是首先进入入口函数,如下图所示:

     

    这里注册了一个“s3c2410-rtc”名称的平台设备驱动

    而“s3c2410-rtc”的平台设备,在./arch/arm/plat-s3c24xx/dev.c里定义了,只不过这里没有注册,如下图所示:

     

    当内核匹配到有与它名称同名的平台设备,就会调用.probe函数,接下来我们便进入s3c2410_rtcdrv->probe函数中看看,做了什么:

    static int s3c_rtc_probe(struct platform_device *pdev)
    {
    struct rtc_device *rtc;           //rtc设备结构体
    struct resource *res;
    int ret;
    
    s3c_rtc_tickno = platform_get_irq(pdev, 1);          //获取IRQ_TICK节拍中断资源
    s3c_rtc_alarmno = platform_get_irq(pdev, 0);        //获取IRQ_RTC闹钟中断资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);   //获取内存资源
    
    s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1,pdev->name);//申请内存资源
    
    s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);     //对内存进行重映射
    
    
    s3c_rtc_enable(pdev, 1);          //设置硬件相关设置,使能RTC寄存器
    
    s3c_rtc_setfreq(s3c_rtc_freq);      //设置TICONT寄存器,使能节拍中断,设置节拍计数值
    
    /*1.注册RTC设备*/
    rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);  
    
    rtc
    ->max_user_freq = 128; platform_set_drvdata(pdev, rtc); return 0; }

    显然最终会调用rtc_device_register()函数来向内核注册rtc_device设备,注册成功会返回一个已注册好的rtc_device,

    而s3c_rtcops是一个rtc_class_ops结构体,里面就是保存如何操作这个rtc设备的函数,比如读写RTC时间,读写闹钟时间等,注册后,会保存在rtc_device->ops里

    该函数在drivers/rtc/Class.c文件内被定义。Class.c文件主要定义了RTC子系统,

    而内核初始化,便会进入Class.c,进入rtc_init()->rtc_dev_init(),来注册字符设备:

        err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");   
            // RTC_DEV_MAX=16,表示只注册0~15个次设备号,设备编号保存在rtc_devt中

    2.它与rtc_device_register()函数注册RTC设备,会有什么关系?

    接下来便来看rtc_device_register(),代码如下:

    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;    //定义一个rtc_device结构体
           ... ...
           rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);  //分配rtc_device结构体为全局变量
    
     
           /*设置rtc_device*/
        rtc->id = id;
           rtc->ops = ops;            //将s3c_rtcops保存在rtc_device->ops里
           rtc->owner = owner;
           rtc->max_user_freq = 64;
           rtc->dev.parent = dev;
           rtc->dev.class = rtc_class;
           rtc->dev.release = rtc_device_release;
           ... ...
    
           rtc_dev_prepare(rtc);                   //1.做提前准备,初始化cdev结构体
           ... ...
           rtc_dev_add_device(rtc);               //2.在/dev下创建rtc相关文件,将cdev添加到系统中
    
           rtc_sysfs_add_device(rtc);             //在/sysfs下创建rtc相关文件
           rtc_proc_add_device(rtc);             //在/proc下创建rtc相关文件
           ... ...
        return rtc;
    }

    上面的rtc_dev_prepare(rtc)和rtc_dev_add_device(rtc)主要做了以下两个(位于./drivers/rtc/rtc-dev.c):

    cdev_init(&rtc->char_dev, &rtc_dev_fops);          //绑定file_operations  
    
    cdev_add(&rtc->char_dev, rtc->dev.devt, 1);    //注册rtc->char_dev字符设备,添加一个从设备到系统中

    显然这里的注册字符设备,和我们上节讲的http://www.cnblogs.com/lifexy/p/7827559.html一摸一样的流程

    所以“s3c2410-rtc”平台设备驱动的.probe主要做了以下几件事:

    • 1.设置RTC相关寄存器
    • 2.分配rtc_device结构体
    • 3.设置rtc_device结构体 
    •     -> 3.1 将struct  rtc_class_ops  s3c_rtcops放入rtc_device->ops,实现对RTC读写时间等操作
    • 4. 注册rtc->char_dev字符设备,且该字符设备的操作结构体为: struct file_operations rtc_dev_fops 

    3.上面的file_operations操作结构体rtc_dev_fops 的成员,如下图所示:

     

    3.1当我们应用层open(”/dev/rtcXX”)时,就会调用rtc_dev_fops-> rtc_dev_open(),我们来看看如何open的:

    static int rtc_dev_open(struct inode *inode, struct file *file)
    {
       struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);//获取对应的rtc_device
       const struct rtc_class_ops *ops = rtc->ops;                            //最终等于s3c_rtcops
    
       file->private_data = rtc;                     //设置file结构体的私有成员等于rtc_device,再次执行ioctl等函数时,直接就可以提取file->private_data即可
    
       err = ops->open ? ops->open(rtc->dev.parent) : 0;  //调用s3c_rtcops->open
    
       mutex_unlock(&rtc->char_lock);
       return err;
    }

    显然最终还是调用rtc_device下的s3c_rtcops->open:

     

    而s3c_rtc_open()函数里主要是申请了两个中断,一个闹钟中断,一个计时中断:

    static int s3c_rtc_open(struct device *dev)
    {     
     struct platform_device *pdev = to_platform_device(dev);    
     struct rtc_device *rtc_dev = platform_get_drvdata(pdev);      
     int ret;
    
     ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,IRQF_DISABLED,  "s3c2410-rtc alarm", rtc_dev);        //申请闹钟中断                      
                  if (ret) {
                  dev_err(dev, "IRQ%d error %d
    ", s3c_rtc_alarmno, ret);
                  return ret;
           }
    
     
    
     ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,IRQF_DISABLED,  "s3c2410-rtc tick", rtc_dev);//申请计时中断   
           if (ret) {
                  dev_err(dev, "IRQ%d error %d
    ", s3c_rtc_tickno, ret);
                  goto tick_err;
           }
    
           return ret;
    
     tick_err:
           free_irq(s3c_rtc_alarmno, rtc_dev);
           return ret;
    }

    3.2 当我们应用层open后,使用 ioctl(int fd, unsigned long cmd, ...)时,就会调用rtc_dev_fops-> rtc_dev_ioctl ():

    static int rtc_dev_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
    {
    struct rtc_device *rtc = file->private_data;  //提取rtc_device
     void __user *uarg = (void __user *) arg;
      ... ...
    
     switch (cmd) {
           case RTC_EPOCH_SET:
           case RTC_SET_TIME:      //设置时间
                  if (!capable(CAP_SYS_TIME))
                         return -EACCES;
                  break;
           case RTC_IRQP_SET:   //改变中断触发速度
           ... ...}
           ... ...
           switch (cmd) {
           case RTC_ALM_READ:    //读闹钟时间
                  err = rtc_read_alarm(rtc, &alarm);              //调用s3c_rtcops-> read_alarm
                  if (err < 0)
                         return err;
    
                  if (copy_to_user(uarg, &alarm.time, sizeof(tm)))  //长传时间数据
                         return -EFAULT;
                         break;
    
           case RTC_ALM_SET:              //设置闹钟时间 , 调用s3c_rtcops-> set_alarm
                  ... ...
    
           case RTC_RD_TIME:              //读RTC时间, 调用s3c_rtcops-> read_alarm
                  ... ...
    
           case RTC_SET_TIME:      //写RTC时间,调用s3c_rtcops-> set_time
                  ... ...
    
           case RTC_IRQP_SET:      //改变中断触发频率,调用s3c_rtcops-> irq_set_freq
                  ... ...
    
    }

    最终还是调用s3c_rtcops下的成员函数,我们以s3c_rtcops-> read_alarm()函数为例,看看如何读出时间的:

    static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
    {
           unsigned int have_retried = 0;
           void __iomem *base = s3c_rtc_base;    //获取RTC相关寄存器基地址

    retry_get_time: /*获取年,月,日,时,分,秒寄存器*/ 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); /* 判断秒寄存器中是0,则表示过去了一分钟,那么小时,天,月,等寄存器中的值都可能已经变化,需要重新读取这些寄存器的值*/ if (rtc_tm->tm_sec == 0 && !have_retried) { have_retried = 1; goto retry_get_time; } /*将获取的寄存器值,转换为真正的时间数据*/ BCD_TO_BIN(rtc_tm->tm_sec); BCD_TO_BIN(rtc_tm->tm_min); BCD_TO_BIN(rtc_tm->tm_hour); BCD_TO_BIN(rtc_tm->tm_mday); BCD_TO_BIN(rtc_tm->tm_mon); BCD_TO_BIN(rtc_tm->tm_year); rtc_tm->tm_year += 100; //存储器中存放的是从1900年开始的时间,所以加上100 rtc_tm->tm_mon -= 1; return 0; }

    同样, 在s3c_rtcops-> set_time()函数里,也是向相关寄存器写入RTC时间

    所以,总结如下所示:

    • rtc_device->char_dev:   字符设备,与应用层、以及更底层的函数打交道
    • rtc_device->ops:    更底层的操作函数,直接操作硬件相关的寄存器,被rtc_device->char_dev调用

     

    4.修改内核

    我们单板上使用ls /dev/rtc*,找不到该字符设备, 因为内核里只定义了s3c_device_rtc这个RTC平台设备,没有注册,所以平台驱动没有被匹配上,接下来我们来修改内核里的注册数组

    4.1进入arch/arm/plat-s3c24xx/Common-smdk.c

    如下图所示,在smdk_devs[]里,添加RTC的平台设备即可,当内核启动时,就会调用该数组,将里面的platform_device统统注册一遍

     

    然后将Common-smdk.c代替虚拟机的内核目录下的Common-smdk.c,重新make uImage编译内核即可

    5.测试运行

    启动后,如下图所示, 使用ls /dev/rtc*,就找到了rtc0这个字符设备

     

    5.1接下来,便开始设置RTC时间

    在linux里有两个时钟:

    硬件时钟(2440里寄存器的时钟)、系统时钟(内核中的时钟)

    所以有两个不同的命令: date命令、hwclock命令

    5.2 date命令使用:

    输入date查看系统时钟:

     

    如果觉得不方便也可以指定格式显示日期,需要在字符串前面加”+”

    如下图所示,输入了  date  "+ %Y/%m/%d %H:%M:%S"

     

    • %M:表示秒
    • %m:表示月
    • %Y:表示年,当只需要最后两位数字,输入%y即可

     

    date命令设置时间格式如下:

    date  月日时分年.秒

    如下图所示,输入date 111515292017.20,即可设置好系统时钟

     

    5.3 hwclock命令使用:

    常用参数如下所示

      -r, --show          读取并打印硬件时钟(read hardware clock and print result )
      -s, --hctosys      将硬件时钟同步到系统时钟(set the system time from the hardware clock )
      -w, --systohc     将系统时钟同步到硬件时钟(set the hardware clock to the current system time )

    如下图所示,使用hwclock -w,即可同步硬件时钟

     

    然后重启后,使用date命令,看到时间正常

     

  • 相关阅读:
    C++ for(char c:s)遍历字符串||for (char c : s)和for (char& c : s)的区别
    二维数组的查找--剑指offer(C++)
    C++学习笔记之--boolalpha
    在C++中matrix.size()和matrix [0] .size()之间的区别是什么?
    C3_note
    用webpack4从零开始构建react脚手架
    php
    正则表达式基础
    DOM
    常用H5
  • 原文地址:https://www.cnblogs.com/lifexy/p/7839625.html
Copyright © 2020-2023  润新知