linux RTC 驱动模型分析

linux RTC 驱动模型分析
        RTC(real time clock)实时时钟,主要作用是给Linux系统提供时间。RTC因为是电池供电的,所以掉电后时间不丢失。Linux内核把RTC用作“离线”的时间与日期维护器。当Linux内核启动时,它从RTC中读取时间与日期,作为基准值。在运行期间内核完全抛开RTC,以软件的形式维护系统的当前时间与日期,并在需要时将时间回写RTC芯片。另外如果RTC提供了IRQ中断并且可以定时,那么RTC还可以作为内核睡眠时唤醒内核的闹钟。应用程序可以用RTC提供的周期中断做一些周期的任务。 linux有两种rtc驱动的接口,一个是老的接口,专门用在PC机上的。另外一钟新接口是基于linux设备驱动程序的。这个新的接口创建了一个RTC驱动模型,实现了RTC的大部分基本功能。而底层驱动无须考虑一些功能的实现,只需将自己注册的RTC核心中,其他工作由RTC核心来完成。下面分析RTC新接口的驱动模型。
一. 驱动模型结构
        与RTC核心有关的文件有:
        /drivers/rtc/class.c          这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口
        /drivers/rtc/rtc-dev.c       这个文件定义了基本的设备文件操作函数,如:open,read等
        /drivers/rtc/interface.c     顾名思义,这个文件主要提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数
        /drivers/rtc/rtc-sysfs.c     与sysfs有关
        /drivers/rtc/rtc-proc.c      与proc文件系统有关
        /include/linux/rtc.h         定义了与RTC有关的数据结构

        RTC驱动模型结构如下图:


二. 基本数据结构
  1. struct rtc_device 结构
[cpp] view plaincopy
  1. struct rtc_device  
  2. {  
  3.     struct device dev;  
  4.     struct module *owner;  
  5.   
  6.     int id;  
  7.     char name[RTC_DEVICE_NAME_SIZE];  
  8.   
  9.     const struct rtc_class_ops *ops;  
  10.     struct mutex ops_lock;  
  11.   
  12.     struct cdev char_dev;  
  13.     unsigned long flags;  
  14.   
  15.     unsigned long irq_data;  
  16.     spinlock_t irq_lock;  
  17.     wait_queue_head_t irq_queue;  
  18.     struct fasync_struct *async_queue;  
  19.   
  20.     struct rtc_task *irq_task;  
  21.     spinlock_t irq_task_lock;  
  22.     int irq_freq;  
  23.     int max_user_freq;  
  24. #ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL  
  25.     struct work_struct uie_task;  
  26.     struct timer_list uie_timer;  
  27.     /* Those fields are protected by rtc->irq_lock */  
  28.     unsigned int oldsecs;  
  29.     unsigned int uie_irq_active:1;  
  30.     unsigned int stop_uie_polling:1;  
  31.     unsigned int uie_task_active:1;  
  32.     unsigned int uie_timer_active:1;  
  33. #endif  
  34. };  
        这个结构是RTC驱动程序的基本数据结构,但是他不像其他核心的基本结构一样,驱动程序以他为参数调用注册函数注册到核心。这个结构是由注册函数返回给驱动程序的。
  2. struct rtc_class_ops 结构
[cpp] view plaincopy
  1. struct rtc_class_ops {  
  2.     int (*open)(struct device *);  
  3.     void (*release)(struct device *);  
  4.     int (*ioctl)(struct device *, unsigned int, unsigned long);  
  5.     int (*read_time)(struct device *, struct rtc_time *);  
  6.     int (*set_time)(struct device *, struct rtc_time *);  
  7.     int (*read_alarm)(struct device *, struct rtc_wkalrm *);  
  8.     int (*set_alarm)(struct device *, struct rtc_wkalrm *);  
  9.     int (*proc)(struct device *, struct seq_file *);  
  10.     int (*set_mmss)(struct device *, unsigned long secs);  
  11.     int (*irq_set_state)(struct device *, int enabled);  
  12.     int (*irq_set_freq)(struct device *, int freq);  
  13.     int (*read_callback)(struct device *, int data);  
  14.     int (*alarm_irq_enable)(struct device *, unsigned int enabled);  
  15.     int (*update_irq_enable)(struct device *, unsigned int enabled);  
  16. };  
        这个结构是RTC驱动程序要实现的基本操作函数,注意这里的操作不是文件操作。驱动程序通过初始化这样一个结构,将自己实现的函数与RTC核心联系起来。这里面的大部分函数都要驱动程序来实现。而且这些函数都是操作底层硬件的,属于最底层的函数。
  3. struct rtc_time 结构
[cpp] view plaincopy
  1. struct rtc_time {  
  2.     int tm_sec;  
  3.     int tm_min;  
  4.     int tm_hour;  
  5.     int tm_mday;  
  6.     int tm_mon;  
  7.     int tm_year;  
  8.     int tm_wday;  
  9.     int tm_yday;  
  10.     int tm_isdst;  
  11. };  
        代表了时间与日期,从RTC设备读回的时间和日期就保存在这个结构体中
三. class.c 
  1. 模块初始化函数:rtc_init
[cpp] view plaincopy
  1. static int __init rtc_init(void)  
  2. {  
  3.     rtc_class = class_create(THIS_MODULE, "rtc");  
  4.     if (IS_ERR(rtc_class)) {  
  5.         printk(KERN_ERR "%s: couldn't create class\n", __FILE__);  
  6.         return PTR_ERR(rtc_class);  
  7.     }  
  8.     rtc_class->suspend = rtc_suspend;  
  9.     rtc_class->resume = rtc_resume;  
  10.     rtc_dev_init();  
  11.     rtc_sysfs_init(rtc_class);  
  12.     return 0;  
  13. }  
        rtc_init首先调用class_create创建了一个类--rtc。我们知道类是一个设备的高层视图,他抽象出了底层的实现细节。类的作用就是向用户空间提供设备的信息,驱动程序不需要直接处理类。然后初始化类结构的相应成员,rtc_suspend,rtc_resume这两个函数也是在class.c中实现的。接下来调用rtc_dev_init(),这个函数为RTC设备动态分配设备号,保存在rtc_devt中。最后调用rtc_sysfs_init,初始化rtc_class的属性。
  2. 为底层驱动提供接口:rtc_device_register,rtc_device_unregister
[cpp] view plaincopy
  1. struct rtc_device *rtc_device_register(const char *name, struct device *dev,  
  2.                     const struct rtc_class_ops *ops,  
  3.                     struct module *owner)  
  4. {  
  5.     struct rtc_device *rtc;  
  6.     int id, err;  
  7.   
  8.     if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) {  
  9.         err = -ENOMEM;  
  10.         goto exit;  
  11.     }  
  12.   
  13.     mutex_lock(&idr_lock);  
  14.     err = idr_get_new(&rtc_idr, NULL, &id);  
  15.     mutex_unlock(&idr_lock);  
  16. /*--------------------(1)---------------------*/  
  17.     if (err < 0)  
  18.         goto exit;  
  19.     id = id & MAX_ID_MASK;  
  20.     rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);  
  21.     if (rtc == NULL) {  
  22.         err = -ENOMEM;  
  23.         goto exit_idr;  
  24.     }  
  25.   
  26.     rtc->id = id;  
  27.     rtc->ops = ops;  
  28.     rtc->owner = owner;  
  29.     rtc->max_user_freq = 64;  
  30.     rtc->dev.parent = dev;  
  31.     rtc->dev.class = rtc_class;  
  32.     rtc->dev.release = rtc_device_release;  
  33.   
  34.     mutex_init(&rtc->ops_lock);  
  35.     spin_lock_init(&rtc->irq_lock);  
  36.     spin_lock_init(&rtc->irq_task_lock);  
  37.     init_waitqueue_head(&rtc->irq_queue);  
  38.   
  39.     strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);  
  40.     dev_set_name(&rtc->dev, "rtc%d", id);  
  41. /*-------------------(2)--------------------*/  
  42.     rtc_dev_prepare(rtc);  
  43.   
  44.   
  45.     err = device_register(&rtc->dev);  
  46.     if (err)  
  47.         goto exit_kfree;  
  48. /*-------------------(3)--------------------*/  
  49.     rtc_dev_add_device(rtc);  
  50.     rtc_sysfs_add_device(rtc);  
  51.     rtc_proc_add_device(rtc);  
  52.   
  53.     dev_info(dev, "rtc core: registered %s as %s\n",  
  54.             rtc->name, dev_name(&rtc->dev));  
  55. /*-------------------(4)--------------------*/  
  56.     return rtc;  
  57.   
  58. exit_kfree:  
  59.     kfree(rtc);  
  60. exit_idr:  
  61.     mutex_lock(&idr_lock);  
  62.     idr_remove(&rtc_idr, id);  
  63.     mutex_unlock(&idr_lock);  
  64. exit:  
  65.     dev_err(dev, "rtc core: unable to register %s, err = %d\n",  
  66.             name, err);  
  67.     return ERR_PTR(err);  
  68. }  
    (1):处理一个idr的结构,idr在linux内核中指的就是整数ID管理机制,从本质上来说,idr是一种将整数ID号和特定指针关联在一起的机制。这个机制最早是在2003年2月加入内核的,当时是作为POSIX定时器的一个补丁。现在,在内核的很多地方都可以找到idr的身影。详细实现请参照相关内核代码。这里从内核中获取一个idr结构,并与id相关联。
    (2):分配了一个rtc_device的结构--rtc,并且初始化了相关的成员:id, rtc_class_ops等等。
    (3):首先调用rtc_dev_prepare(在rtc-dev.c中定义)。因为RTC设备本质来讲还是字符设备,所以这里初始化了字符设备相关的结构:设备号以及文件操作。然后调用device_register将设备注册到linux设备模型核心。这样在模块加载的时候,udev daemon就会自动为我们创建设备文件rtc(n)。
    (4):先后调用rtc_dev_add_device,rtc_sysfs_add_device,rtc_proc_add_device三个函数。rtc_dev_add_device注册字符设备,rtc_sysfs_add_device只是为设备添加了一个闹钟属性,rtc_proc_add_device 创建proc文件系统接口。
四. rtc-dev.c 
        rtc-dev.c 初始化了一个file_operations结构--rtc_dev_fops,并定义了这些操作函数。
  1. rtc_dev_fops rtc基本的文件操作
[cpp] view plaincopy
  1. static const struct file_operations rtc_dev_fops = {  
  2. .owner      = THIS_MODULE,  
  3. .llseek     = no_llseek,  
  4. .read       = rtc_dev_read,  
  5. .poll       = rtc_dev_poll,  
  6. .unlocked_ioctl = rtc_dev_ioctl,  
  7. .open       = rtc_dev_open,  
  8. .release    = rtc_dev_release,  
  9. .fasync     = rtc_dev_fasync,  
  10. ;   
  2. 函数的实现(以rtc_dev_read为例)   
[cpp] view plaincopy
  1. rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)  
  2. {  
  3.     struct rtc_device *rtc = file->private_data;  
  4.   
  5.     DECLARE_WAITQUEUE(wait, current);  
  6.     unsigned long data;  
  7.     ssize_t ret;  
  8.     if (count != sizeof(unsigned int) && count < sizeof(unsigned long))  
  9.         return -EINVAL;  
  10.     add_wait_queue(&rtc->irq_queue, &wait);  
  11.     do {  
  12.         __set_current_state(TASK_INTERRUPTIBLE);  
  13.   
  14.         spin_lock_irq(&rtc->irq_lock);  
  15.         data = rtc->irq_data;  
  16.         rtc->irq_data = 0;  
  17.         spin_unlock_irq(&rtc->irq_lock);  
  18.   
  19.         if (data != 0) {  
  20.             ret = 0;  
  21.             break;  
  22.         }  
  23.         if (file->f_flags & O_NONBLOCK) {  
  24.             ret = -EAGAIN;  
  25.             break;  
  26.         }  
  27.         if (signal_pending(current)) {  
  28.             ret = -ERESTARTSYS;  
  29.             break;  
  30.         }  
  31.         schedule();  
  32.     } while (1);  
  33.     set_current_state(TASK_RUNNING);  
  34.     remove_wait_queue(&rtc->irq_queue, &wait);  
  35.   
  36.     if (ret == 0) {  
  37.         /* Check for any data updates */  
  38.         if (rtc->ops->read_callback)  
  39.             data = rtc->ops->read_callback(rtc->dev.parent,  
  40.                                data);  
  41.   
  42.         if (sizeof(int) != sizeof(long) &&  
  43.             count == sizeof(unsigned int))  
  44.             ret = put_user(data, (unsigned int __user *)buf) ?:  
  45.                 sizeof(unsigned int);  
  46.         else  
  47.             ret = put_user(data, (unsigned long __user *)buf) ?:  
  48.                 sizeof(unsigned long);  
  49.     }  
  50.     return ret;  
  51. }  
        这里的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,进程会被调度出可运行队列,从而让出处理器,真正进入睡眠。跳出循环代表被唤醒,然后将进程状态改变为可运行,移除等待队列入口。最后将读回的数据传给用户空间。
五. interface.c 
        interface.c里的所有函数的实现都对应于rtc-dev.c 中ioctl相应的命令。对应关系如下:
RTC_ALM_READ                     rtc_read_alarm           读取闹钟时间
RTC_ALM_SET                      rtc_set_alarm            设置闹钟时间
RTC_RD_TIME                      rtc_read_time            读取时间与日期
RTC_SET_TIME                     rtc_set_time             设置时间与日期
RTC_PIE_ON RTC_PIE_OFF           rtc_irq_set_state              开关RTC全局中断的函数
RTC_AIE_ON RTC_AIE_OFF           rtc_alarm_irq_enable     使能禁止RTC闹钟中断
RTC_UIE_OFF RTC_UIE_ON           rtc_update_irq_enable    使能禁止RTC更新中断
RTC_IRQP_SET                     rtc_irq_set_freq         设置中断的频率
       以上就是所有ioctl的命令与实现的对应关系。其中如果不涉及中断的话,有两个命令需要我们特别关心一下,就是RTC_RD_TIME与RTC_SET_TIME。因为RTC最基本的功能就是提供时间与日期。这两个命令恰恰是获取时间和设置时间。下面分析一下这两个命令的实现,也就是rtc_set_alarm与rtc_read_time函数的实现:
  1. rtc_read_time 函数
[cpp] view plaincopy
  1. int rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)  
  2. {  
  3.     int err;  
  4.   
  5.     err = mutex_lock_interruptible(&rtc->ops_lock);  
  6.     if (err)  
  7.         return err;  
  8.   
  9.     if (!rtc->ops)  
  10.         err = -ENODEV;  
  11.     else if (!rtc->ops->read_time)  
  12.         err = -EINVAL;  
  13.     else {  
  14.         memset(tm, 0, sizeof(struct rtc_time));  
  15.         err = rtc->ops->read_time(rtc->dev.parent, tm);  
  16.     }  
  17.   
  18.     mutex_unlock(&rtc->ops_lock);  
  19.     return err;  
  20. }  
        这个函数用了一个信号来保证在同一时刻只有一个进程可以获取时间。锁定了这个信号量后,调用rtc->ops里面read函数,这个函数是由具体的驱动程序实现的,操作底层硬件。读回的时间存放在rtc_time结构里面的。
  2. rtc_set_time 函数
[cpp] view plaincopy
  1. int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)  
  2. {  
  3.     int err;  
  4.   
  5.     err = rtc_valid_tm(tm);  
  6.     if (err != 0)  
  7.         return err;  
  8.   
  9.     err = mutex_lock_interruptible(&rtc->ops_lock);  
  10.     if (err)  
  11.         return err;  
  12.   
  13.     if (!rtc->ops)  
  14.         err = -ENODEV;  
  15.     else if (rtc->ops->set_time)  
  16.         err = rtc->ops->set_time(rtc->dev.parent, tm);  
  17.     else if (rtc->ops->set_mmss) {  
  18.         unsigned long secs;  
  19.         err = rtc_tm_to_time(tm, &secs);  
  20.         if (err == 0)  
  21.             err = rtc->ops->set_mmss(rtc->dev.parent, secs);  
  22.     } else  
  23.         err = -EINVAL;  
  24.   
  25.     mutex_unlock(&rtc->ops_lock);  
  26.     return err;  
  27. }  
        这个函数其实和rtc_read_time函数差不多,同样是锁定信号量,同样是调用底层驱动函数。但是这里的设置时间提供了两个调用:一个是set_time,一个是set_mmss。因为有的RTC硬件只计算秒数,不关心墙钟时间,所以如果是这样的RTC,必须实现set_mmss来设置时间。
六. rtc-sysfs.c 部分
        这个部分主要是有关sysfs的操作。rtc-sysfs.c中定义了这样一个设备属性组,如下:
[cpp] view plaincopy
  1. static struct device_attribute rtc_attrs[] = {  
  2.     __ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),  
  3.     __ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),  
  4.     __ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),  
  5.     __ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),  
  6.     __ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,  
  7.           rtc_sysfs_set_max_user_freq),  
  8.     __ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),  
  9.      { },  
  10. };  

       这个属性组是在class.c的模块初始化函数中,由rtc_sysfs_init函数赋值给rtc_class->dev_attrs的,以后属于这个类的设备都会有这些属性。但是我们知道要想一个设备结构拥有一种属性,必须调用device_create_file,这样才会使这个属性出现在sysfs相关设备目录里。但是在这里的代码中只是给这个类的dev_attrs域赋值了这个属性组指针,而没有调用device_create_file。我原来以为是在rtc_device_resgister函数中,由rtc_sysfs_add_device完成这个工作,但是这个函数只是给设备添加了闹钟属性,并没有处理这个属性组。最后发现这个工作是由device_register来完成的。这里的调用关系有点复杂:

device_register调用device_add

device_add调用 device_add_attrs

 device_add_attrs调用device_add_attributes

device_add_attributes调用device_create_file来完成设备的属性设置的。

        设置完属性后,在/sys/class/rtc/rtc(n)的目录下就会出现name,date,time等文件,用户读这些文件的时候就会调用相应的函数。如读取name文件,就会调用rtc_sysfs_show_name函数,这个函数也是在rtc-sysfs.c中实现的,作用是读取并显示时间。

七. rtc-proc.c 
       这个文件提供RTC的proc文件系统接口。proc文件系统是软件创建的文件系统,内核通过他向外界导出信息,下面的每一个文件都绑定一个函数,当用户读取这个文件的时候,这个函数会向文件写入信息。rtc-proc.c中初始化了一个文件操作:
[cpp] view plaincopy
  1. static const struct file_operations rtc_proc_fops = {  
  2.     .open       = rtc_proc_open,  
  3.     .read       = seq_read,  
  4.     .llseek     = seq_lseek,  
  5.     .release    = rtc_proc_release,  
  6. };  
        RTC驱动在向RTC核心注册自己的时候,由注册函数rtc_device_resgister调用rtc_proc_add_device来实现proc接口的初始化,这个函数如下定义:
[cpp] view plaincopy
  1. void rtc_proc_add_device(struct rtc_device *rtc)  
  2. {  
  3.     if (rtc->id == 0)  
  4.         proc_create_data("driver/rtc", 0, NULL, &rtc_proc_fops, rtc);  
  5. }  
        他主要调用了proc_create_data。proc_create_data完成创建文件节点的作用,并将文件的操作函数与节点联系起来。调用这个函数后,在/proc/driver目录下就会有一个文件rtc,应用程序打开这个文件就会调用rtc_proc_open函数,这个函数如下定义:
[cpp] view plaincopy
  1. static int rtc_proc_open(struct inode *inode, struct file *file)  
  2. {  
  3.         struct rtc_device *rtc = PDE(inode)->data;  
  4.         if (!try_module_get(THIS_MODULE))  
  5.                 return -ENODEV;  
  6.        return single_open(file, rtc_proc_show, rtc);  
  7. }  
        我们知道一个proc的文件必须与一个操作函数组成一个proc入口项,这个文件才能正常工作。这个函数最主要作用就是调用single_open,创建一个proc文件入口项,使其操作函数是rtc_proc_show,并初始化seq_file接口。rtc_proc_show函数如下定义:
[cpp] view plaincopy
  1. static int rtc_proc_show(struct seq_file *seq, void *offset)  
  2. {  
  3.     int err;  
  4.     struct rtc_device *rtc = seq->private;  
  5.     const struct rtc_class_ops *ops = rtc->ops;  
  6.     struct rtc_wkalrm alrm;  
  7.     struct rtc_time tm;  
  8.   
  9.     err = rtc_read_time(rtc, &tm);  
  10.     if (err == 0) {  
  11.         seq_printf(seq,  
  12.             "rtc_time\t: %02d:%02d:%02d\n"  
  13.             "rtc_date\t: %04d-%02d-%02d\n",  
  14.             tm.tm_hour, tm.tm_min, tm.tm_sec,  
  15.             tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);  
  16.     }  
  17.   
  18.     err = rtc_read_alarm(rtc, &alrm);  
  19.     if (err == 0) {  
  20.         seq_printf(seq, "alrm_time\t: ");  
  21.         if ((unsigned int)alrm.time.tm_hour <= 24)  
  22.             seq_printf(seq, "%02d:", alrm.time.tm_hour);  
  23.         else  
  24.             seq_printf(seq, "**:");  
  25.         if ((unsigned int)alrm.time.tm_min <= 59)  
  26.             seq_printf(seq, "%02d:", alrm.time.tm_min);  
  27.         else  
  28.             seq_printf(seq, "**:");  
  29.         if ((unsigned int)alrm.time.tm_sec <= 59)  
  30.             seq_printf(seq, "%02d\n", alrm.time.tm_sec);  
  31.         else  
  32.             seq_printf(seq, "**\n");  
  33.   
  34.         seq_printf(seq, "alrm_date\t: ");  
  35.         if ((unsigned int)alrm.time.tm_year <= 200)  
  36.             seq_printf(seq, "%04d-", alrm.time.tm_year + 1900);  
  37.         else  
  38.             seq_printf(seq, "****-");  
  39.         if ((unsigned int)alrm.time.tm_mon <= 11)  
  40.             seq_printf(seq, "%02d-", alrm.time.tm_mon + 1);  
  41.         else  
  42.             seq_printf(seq, "**-");  
  43.         if (alrm.time.tm_mday && (unsigned int)alrm.time.tm_mday <= 31)  
  44.             seq_printf(seq, "%02d\n", alrm.time.tm_mday);  
  45.         else  
  46.             seq_printf(seq, "**\n");  
  47.         seq_printf(seq, "alarm_IRQ\t: %s\n",  
  48.                 alrm.enabled ? "yes" : "no");  
  49.         seq_printf(seq, "alrm_pending\t: %s\n",  
  50.                 alrm.pending ? "yes" : "no");  
  51.     }  
  52.   
  53.     seq_printf(seq, "24hr\t\t: yes\n");  
  54.   
  55.     if (ops->proc)  
  56.         ops->proc(rtc->dev.parent, seq);  
  57.   
  58.     return 0;  
  59. }  
        这个函数就是最后给用户显示信息的函数了,可以看出他通过调用rtc_deivce中的操作函数,读取时间,日期和一些其他的信息显示给用户。 
六. 总结

        RTC核心使底层硬件对用户来说是透明的,并且减少了编写驱动程序的工作量。RTC新的驱动接口提供了更多的功能,使系统可以同时存在多个RTC。/dev,sysfs,proc这三种机制的实现使得应用程序能灵活的使用RTC,RTC核心虽然表面上看上去很简单,但是还是涉及到很多知识,有些东西书上讲的还是不够详细,还需要通过分析代码加深理解。 另外RTC核心代码的组织方式也值得学习,不同功能的代码放在不同的文件中,简单明了。  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/405267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Install Docker Mac OS X

检查 Mac OS version 要求必须是 OS X 10.6 Snow Leopard or newer to run Boot2Docker安装 Boot2Docker 列表内容下载地址&#xff1a;https://github.com/boot2docker/osx-installer/releases/download/v1.7.0/Boot2Docker-1.7.0.pkg 下载后点击安装&#xff0c;就是按照提示…

linq to sql报错,

以上是由于我把关联表中的string类型写成int类型所导致的&#xff0c;记一下&#xff0c;备用。转载于:https://www.cnblogs.com/server126/archive/2011/05/25/2057416.html

[VC6] RadioBox使用入门

基于对话框的应用程序&#xff0c;界面如下&#xff1a; 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 具体请参考代码&#xff1a; 关键代码&#xff1a; // del2Dlg.cpp : implementation file //#include "stdaf…

树莓派使用STEP1:装系统

1、windows安装SD卡格式化软件&#xff1a;SDFormatter.exe 2、windows安装系统烧录软件Win32DiskImager.exe 3、下载镜像&#xff1a;https://www.raspberrypi.org/downloads/raspbian/ 并解压缩 4、用Win32DiskImager将下载的镜像文件烧录进SD卡。 5、插上SD到树莓派&…

树莓派使用STEP2:设置网络

安装好系统并开机&#xff0c;插入鼠标键盘和显示器&#xff0c;登陆系统&#xff0c;打开终端。按以下操作。 1、sudo raspi-config 进入配置&#xff0c;打开SSH功能。 Interfacing Options -> SSH 2、扩展系统内存。 Advanced options -> Expand Filesystem 3、设置…

linux3.0-内核自带led驱动移植

********************************************************************************************************************************************************************************** cpu : s3c2440(arm9) linux内核&#xff1a;linux-3.0 开发板 &am…

树莓派使用STEP3:更换镜像源

更换国内镜像源&#xff0c;这里使用中科大的源&#xff0c;软件下载和更新速度更快。 1、sudo nano /etc/apt/sources.list 2、sudo nano /etc/apt/sources.list.d/raspi.list 3、sudo apt-get update && apt-get upgrade

树莓派使用STEP4:安装vim

系统原装的vi操作对新手和学习者不友好&#xff0c;可以用nano编辑器&#xff0c;因为我比较熟悉vi和vim&#xff0c;这里推荐使用vim。首先需要删除原装的vi&#xff0c;然后重新安装新的vim&#xff0c;过程比较简单。 1、卸载预装的vi sudo apt-get remove vim-common 2、…

手机测试pc端网页

在这个问题上徘徊了 一个钟头了&#xff0c;终于被我找到方法了&#xff0c;就赶紧记下来&#xff0c;以后好查阅&#xff01;&#xff01; 主要问题在防火墙&#xff0c;防火墙阻当了80端口&#xff0c;所以怎么用手机访问都是访问不了的。把防火墙关闭就好了&#xff01; 贴上…

树莓派使用STEP5:安装samba文件共享服务器

samba服务器可以在多平台多操作系统搭建文件服务器&#xff0c;用于共享文件。为了方便windows和树莓派交换文件&#xff0c;将samba服务器的搭建过程记录如下。 1、安装samba服务器。 sudo apt-get install samba samba-common-bin 出现以下提示&#xff1a;Modify smb.conf…

ETH—Lwip以太网通信

第39章 ETH—Lwip以太网通信 全套200集视频教程和1000页PDF教程请到秉火论坛下载&#xff1a;www.firebbs.cn 野火视频教程优酷观看网址&#xff1a;http://i.youku.com/firege 互联网技术对人类社会的影响不言而喻。当今大部分电子设备都能以不同的方式接入互联网(Inter…

计算从A地出发到各个地方的路径及距离

数据库环境&#xff1a;SQL SERVER 2005 如题&#xff0c;现有bus表数据如下&#xff0c;dstart是起点&#xff0c;dend是终点&#xff0c;distance是两地的距离。 求从A地出发到各个地方的距离。 有经验的人一看&#xff0c;就知道题目关于树形查询的。SQL SERVER 2005数据库…

树莓派使用STEP6:安装git

git用于创建和管理代码仓&#xff0c;是一个很优秀的版本控制工具。linux/树莓派安装非常简单。 1、sudo apt-get install git-core

cJSON 使用笔记

缘 起 最近在stm32f103上做一个智能家居的项目&#xff0c;其中选择的实时操作系统是 rt_thread OS v1.2.2稳定版本&#xff0c;其中涉及到C和java(android)端数据…

使用虚拟路径时出现404问题

今天在做一个小项目的时候使用了如下路径 web.xml如下&#xff1a; 一切配置都正确&#xff0c;可还是404 在折腾了半天之后发现浏览器的地址栏没有项目名&#xff01;也就是说连接到项目外面去了&#xff0c;果断404。然后才记得虚拟路径前面的“/”和通常用的“../”具有同样…

树莓派使用STEP7:安装wiringPi硬件外设驱动C库

基于C/C开发树莓派外设&#xff0c;比较好的一种库是wiringPi&#xff0c;这里记录安装的流程和步骤。 一、在线安装 1、控制命令行 &#xff1a;git clone git://git.drogon.net/wiringPi 克隆git代码仓库 2、控制命令行&#xff1a; git pull origin 拉取最新的wiringPi…

STM32的I2C主从机通信

最近一段时间在做I2C通信协议&#xff0c;需要在两块STM32之间做I2C通信&#xff0c;定的是主机用IO口模拟&#xff0c;从机用的是STM32的硬件I2C&#xff0c;我的项目要求是需要主从机之间能够进行一串数据收发而不出错&#xff0c;实验时在主机方面&#xff0c;利用IO口模拟主…

css垂直居中那点事

这是我技术博客生涯的第一篇文章&#xff0c;想想还是有点小鸡冻。。。菜鸟的征程现在要开始了 学习css的时候经常被各种问题纠结到不要不要的&#xff0c;没办法&#xff0c;只能写写博客帮助整理一下自己的思绪和帮助一下和我遇到同样问题的小伙伴们 不知道各位学习css的小伙…

Windows常用shell命令大全

From: http://blog.csdn.net/yunzhongfeiniao/article/details/6564577 基于鼠标操作的后果就是OS界面外观发生改变&#xff0c;就得多花学习成本。更主要的是基于界面引导Path与命令行直达速度是难以比拟的。另外Geek很大一部分是键盘控&#xff0c;而非鼠标流的。 整理Wind…

div模拟select/option解决兼容性问题及增加可拓展性

个人博客&#xff1a; http://mcchen.club 想到做这个模拟的原因是之前使用select>option标签的时候发现没有办法操控option的很多样式&#xff0c;比如line-height等&#xff0c;还会由此导致在IE8及以下版本浏览器中的各种问题。 这个模拟思路很简单&#xff0c;也很清晰&…