专栏首页科技分享Linux RTC驱动模型分析之rtc-sysfs.c【转】

Linux RTC驱动模型分析之rtc-sysfs.c【转】

转自:https://blog.csdn.net/longwang155069/article/details/52353408

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/longwang155069/article/details/52353408 rtc节点 rtc-sysfs文件主要的操作就是在sys下创建rtc的属性节点,可以方便用户方便快捷的访问,查找问题。下来大概看看sys下的rtc节点,有个直观的认识。 [root@test ~]# cat /sys/class/rtc/rtc0/ date hctosys power/ time dev max_user_freq since_epoch uevent device/ name subsystem/ wakealarm 这是手机上rtc的节点属性,可以看到手机上只有一个rtc0设备。也可以查看/dev/rtc0设备 [root@test ~]# ls -l /dev/rtc0 crw-rw---- 1 root root 254, 0 Jan 1 1970 /dev/rtc0 可以看到rtc的主设备号是254, 次设备号是0。这些信息也可以在/proc/devices下看到。 [root@test ~]# cat /proc/devices Character devices: 1 mem 2 pty ... 254 rtc 也可以看到rtc的主设备号是254, 这都是通过上一节说的rtc-dev.c中注册得到的。

rtc-sysfs.c分析 void __init rtc_sysfs_init(struct class *rtc_class) { rtc_class->dev_groups = rtc_groups; } 设置rtc的设备组属性,rtc_groups是一个attribute_group的结构体。这个函数会在class.c中rtc_init中调用到,关于rtc_group会在后面说到。

static inline int rtc_does_wakealarm(struct rtc_device *rtc) { if (!device_can_wakeup(rtc->dev.parent)) //用来判断是否具有wakeup的能力 return 0; return rtc->ops->set_alarm != NULL; //用来判断是否具有alarm的能力 } 该函数是用来检测rtc是否支持wakeup功能和alarm功能。 wakeup的能力就是能唤醒suspend-to-RAM/suspend-to-disk设备。wakeup的能力是通过如下代码: static inline bool device_can_wakeup(struct device *dev) { return dev->power.can_wakeup; } 也就是判断can_wakeup是否为true,至于rtc是否支持就需要看对应的rtc驱动是否实现该功能。 比如驱动: rtc-ds1305.c中就调用如下的代码设置wakeup的能力。 device_set_wakeup_capable(&spi->dev, 1); 也可以通过如下方式判断是否支持wakeup功能: root@test:/ # cat /sys/class/rtc/rtc0/device/power/wakeup enabled 显示enabled就代表此rtc支持 wakeup功能,也就是说有唤醒suspend/standby的系统或者设备。

而对于rtc是否支持alarm功能,就通过驱动的ops操作函数集合看set_alarm有没有实现就ok。 如果rtc即支持wakeup功能也支持alarm功能,则: void rtc_sysfs_add_device(struct rtc_device *rtc) { int err; /* not all RTCs support both alarms and wakeup */ if (!rtc_does_wakealarm(rtc)) //检测是否支持wakeup和alarm功能 return; err = device_create_file(&rtc->dev, &dev_attr_wakealarm); //创建wakealarm属性 if (err) dev_err(rtc->dev.parent, "failed to create alarm attribute, %d\n", err); } 如果rtc都支持wakup和alarm功能,就创建wakealarm属性节点。否则不创建。

接下来分析wakealarm属性的show和store函数。 static DEVICE_ATTR(wakealarm, S_IRUGO | S_IWUSR, rtc_sysfs_show_wakealarm, rtc_sysfs_set_wakealarm); 这里出现了DEVICE_ATTR,有必要说一下这个宏定义。 <kernel/include/liunux/device.h> --------------------------------------------------------------------------------- #define DEVICE_ATTR(_name, _mode, _show, _store) \ struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) #define __ATTR(_name, _mode, _show, _store) { \ .attr = {.name = __stringify(_name), \ .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ .show = _show, \ .store = _store, \ }

以上就是DEVICE_ATTR的宏定义,则按照定义将wakealarm的属性展开,如下: struct device_attribute dev_attr_wakealarm { .name = wakealarm, .mode = S_IRUGO | S_IWUSR, .show = rtc_sysfs_show_wakealarm, .store = rtc_sysfs_set_wakealarm, } 上面的属性可以知道,wakealarm的属性为可读可写的,当cat wakealarm的时候最终调用show函数,echo的时候最终调用strore函数。 root@test:/ # cat /sys/class/rtc/rtc0/wakealarm root@test:/ # 当读wakealarm的时候,没有任何值,说明目前没有设备alarm。 也可以通过cat /proc/driver/rtc获得更多的信息: rtc_time : 07:07:46 rtc_date : 2012-01-01 alrm_time : 00:00:00 alrm_date : 1970-01-01 alarm_IRQ : no alrm_pending : no update IRQ enabled : no periodic IRQ enabled : no periodic IRQ frequency : 1 max user IRQ frequency : 64 24hr : yes 可以看到alarm_IRQ是no, 当设置正确的alarm值后就会变为yes的。接下来设置当前的时间之后的100s root@test:/ # echo +100 > /sys/class/rtc/rtc0/wakealarm root@test:/ # cat /proc/driver/rtc rtc_time : 07:09:32 rtc_date : 2012-01-01 alrm_time : 07:11:05 alrm_date : 2012-01-01 alarm_IRQ : yes alrm_pending : no update IRQ enabled : no periodic IRQ enabled : no periodic IRQ frequency : 1 max user IRQ frequency : 64 24hr : yes 可以看到alrm_time变为当前时间+100s了,同时alarm_IRQ也变为yes。 同时再次cat wakealarm,即可获得值。 root@test:/ # cat /sys/class/rtc/rtc0/wakealarm 1325401865 此值是unix的时间戳,必须要转换为UTC时间,可以通过如下的网址转换,http://tool.chinaz.com/Tools/unixtime.aspx

可以看到转换后的时间是2012/1/1 15:11:5,为什么感觉和alrm_time对不上呢? 那是因为北京在东八区,相差8个小时,15-8=7则就是alrm_time。 当cat wakealarm有值的时候,再次echo值进如wakealarm的时候就会出现设备忙,所以再次设备时候必须清除以前的设置。 root@test:/ # cat /sys/class/rtc/rtc0/wakealarm 1325401865 root@test:/ # echo +100 > /sys/class/rtc/rtc0/wakealarm sh: echo: write error: Device or resource busy 那如何就可以清空wakealarm的值,可以通过echo 0 > wakealarm就可以清空 root@test:/ # cat /sys/class/rtc/rtc0/wakealarm 1325401865 root@test:/ # echo 0 > /sys/class/rtc/rtc0/wakealarm root@test:/ # cat /sys/class/rtc/rtc0/wakealarm root@test:/ # 在知道了上述的设置之后,再来看代码,在看代码之前先看一下必要的数据结构。 struct rtc_time { int tm_sec; //秒 int tm_min; //分钟 int tm_hour; //小时 int tm_mday; //一月中的第几天 int tm_mon; //月份 int tm_year; //年份 int tm_wday; //周 int tm_yday; //一年中的第几天 int tm_isdst; //夏令时标识符 }; /* * This data structure is inspired by the EFI (v0.92) wakeup * alarm API. */ struct rtc_wkalrm { unsigned char enabled; /* 0 = alarm disabled, 1 = alarm enabled */ unsigned char pending; /* 0 = alarm not pending, 1 = alarm pending */ struct rtc_time time; /* time the alarm is set to */ }; 先分析store函数,当之后往里面写值的之后,才可以read出来。 static ssize_t rtc_sysfs_set_wakealarm(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { ssize_t retval; unsigned long now, alarm; unsigned long push = 0; struct rtc_wkalrm alm; struct rtc_device *rtc = to_rtc_device(dev); char *buf_ptr; int adjust = 0; /* Only request alarms that trigger in the future. Disable them * by writing another time, e.g. 0 meaning Jan 1 1970 UTC. //设置时间必须是在将来 */ retval = rtc_read_time(rtc, &alm.time); //读取当前时间 if (retval < 0) return retval; rtc_tm_to_time(&alm.time, &now); //将当前的时间转化为从1970来经历的秒数 buf_ptr = (char *)buf; if (*buf_ptr == '+') { //如果按照我们上面的设置,echo +100 > wakealarm, 则buf就是+100, 然后解析buf buf_ptr++; if (*buf_ptr == '=') { buf_ptr++; push = 1; } else adjust = 1; //执行到这里 } alarm = simple_strtoul(buf_ptr, NULL, 0); //将“100”转化为数字 if (adjust) { alarm += now; //alarm就是当前时间+100 } if (alarm > now || push) { /* Avoid accidentally clobbering active alarms; we can't * entirely prevent that here, without even the minimal * locking from the /dev/rtcN api. */ retval = rtc_read_alarm(rtc, &alm); //读取alarm时间 if (retval < 0) return retval; if (alm.enabled) { //第一次是没有使能的,如果第二次设置的话 if (push) { rtc_tm_to_time(&alm.time, &push); alarm += push; } else return -EBUSY; //就会出现设备忙,在上面已经演示过了 } else if (push) return -EINVAL; alm.enabled = 1; //先使能 } else { alm.enabled = 0; /* Provide a valid future alarm time. Linux isn't EFI, * this time won't be ignored when disabling the alarm. */ alarm = now + 300; } rtc_time_to_tm(alarm, &alm.time); //又将秒数设置为农历时间格式 retval = rtc_set_alarm(rtc, &alm); //设置alarm时间 return (retval < 0) ? retval : n; } 下面分析read操作,最终调用show函数。 static ssize_t rtc_sysfs_show_wakealarm(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t retval; unsigned long alarm; struct rtc_wkalrm alm; /* Don't show disabled alarms. For uniformity, RTC alarms are * conceptually one-shot, even though some common RTCs (on PCs) * don't actually work that way. * * NOTE: RTC implementations where the alarm doesn't match an * exact YYYY-MM-DD HH:MM[:SS] date *must* disable their RTC * alarms after they trigger, to ensure one-shot semantics. */ retval = rtc_read_alarm(to_rtc_device(dev), &alm); //读取alarm的值 if (retval == 0 && alm.enabled) { //如果enable了,然后显示 rtc_tm_to_time(&alm.time, &alarm); retval = sprintf(buf, "%lu\n", alarm); } return retval; 分析完wakealarm节点之后,还有一系列节点是rtc共有的,如下: static struct attribute *rtc_attrs[] = { &dev_attr_name.attr, &dev_attr_date.attr, &dev_attr_time.attr, &dev_attr_since_epoch.attr, &dev_attr_max_user_freq.attr, &dev_attr_hctosys.attr, NULL, }; ATTRIBUTE_GROUPS(rtc); 在这里需要将ATTRIBUTE_GROOUP(rtc)展开,展开之后就是: static const struct attribute_group rtc_group = { .attrs = rtc_attrs, } static const struct attribute_group *rtc_groups[]={ &rtc_group, null } 而rtc_groups就是在rtc_sysfs_init赋值给dev_groups的。 在device_add_attrs函数中会添加这些属性,如下: static int device_add_attrs(struct device *dev) { struct class *class = dev->class; const struct device_type *type = dev->type; int error; if (class) { error = device_add_groups(dev, class->dev_groups); if (error) return error; } 明白上述的创建原理之后,再依次看每个节点的意思。 static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%s\n", to_rtc_device(dev)->name); } static DEVICE_ATTR_RO(name); 只读属性,显示rtc设备的名称,在驱动中会有该rtc对应的名称。 static ssize_t date_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t retval; struct rtc_time tm; retval = rtc_read_time(to_rtc_device(dev), &tm); if (retval == 0) { retval = sprintf(buf, "%04d-%02d-%02d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); } return retval; } static DEVICE_ATTR_RO(date); static ssize_t time_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t retval; struct rtc_time tm; retval = rtc_read_time(to_rtc_device(dev), &tm); if (retval == 0) { retval = sprintf(buf, "%02d:%02d:%02d\n", tm.tm_hour, tm.tm_min, tm.tm_sec); } return retval; } static DEVICE_ATTR_RO(time); 上述的两个只读属性,一个是当前的时间,一个是当前的日期,不做过多解释。 static ssize_t since_epoch_show(struct device *dev, struct device_attribute *attr, char *buf) { ssize_t retval; struct rtc_time tm; retval = rtc_read_time(to_rtc_device(dev), &tm); if (retval == 0) { unsigned long time; rtc_tm_to_time(&tm, &time); retval = sprintf(buf, "%lu\n", time); } return retval; } static DEVICE_ATTR_RO(since_epoch); 只读属性,该属性的值表示当前的时间转换为自1970年来的秒数。 static ssize_t max_user_freq_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", to_rtc_device(dev)->max_user_freq); } static ssize_t max_user_freq_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { struct rtc_device *rtc = to_rtc_device(dev); unsigned long val = simple_strtoul(buf, NULL, 0); if (val >= 4096 || val == 0) return -EINVAL; rtc->max_user_freq = (int)val; return n; } static DEVICE_ATTR_RW(max_user_freq); 可读可写属性,show函数是读取最大的freq, store是设置最大的频率,不能超过4096.

/** * rtc_sysfs_show_hctosys - indicate if the given RTC set the system time * * Returns 1 if the system clock was set by this RTC at the last * boot or resume event. */ static ssize_t hctosys_show(struct device *dev, struct device_attribute *attr, char *buf) { #ifdef CONFIG_RTC_HCTOSYS_DEVICE if (rtc_hctosys_ret == 0 && strcmp(dev_name(&to_rtc_device(dev)->dev), CONFIG_RTC_HCTOSYS_DEVICE) == 0) return sprintf(buf, "1\n"); else #endif return sprintf(buf, "0\n"); } static DEVICE_ATTR_RO(hctosys); 只读属性,如果返回1代表系统的clock最近一次使用rtc设置。返回0代表没有。 --------------------- 作者:Loopers 来源:CSDN 原文:https://blog.csdn.net/longwang155069/article/details/52353408 版权声明:本文为博主原创文章,转载请附上博文链接!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 调试 – 观察Linux内核中的变量(内存地址)更改,并在更改时打印堆栈跟踪?【转】

    脚本之家收集整理的这篇文章主要介绍了调试 – 观察Linux内核中的变量(内存地址)更改,并在更改时打印堆栈跟踪?,脚本之家小编觉得挺不错的,现在分享给大家,也...

    用户3033338
  • [dts]Device Tree机制

    转自:http://blog.csdn.net/machiner1/article/details/47805069

    用户3033338
  • [dts]Device Tree机制【转】

    转自:https://www.cnblogs.com/aaronLinux/p/5496559.html

    用户3033338
  • Linux RTC驱动模型分析

    RTC(real-time clock)简称实时时钟,主要作用是用来记时,产生闹钟等。RTC因为有备份电池,所以即使计算机关机掉电,也不会影响RTC记时。而RT...

    DragonKingZhu
  • 【干货】17张思维导图,一网打尽机器学习统计基础

    本文用一系列「思维导图」由浅入深的总结了「统计学」领域的基础知识,是对之前系列文章做的一次完整的梳理,也是我至今为止所有与统计有关的学习笔记。普通人难以做到“一...

    钱塘数据
  • 自媒体作者评奖活动 (10月)

    为鼓励参与腾讯云自媒体计划的作者产出更多优质内容,云+社区将试运行自媒体作者月度评奖活动(以下简称活动)。

    云加社区
  • HTML-JavaScript动态添加元素appendChild

    开始自学一下前端的基础知识,这里记录一下知识点,毕竟不熟,不记录很容易就忘记了。前端最佳的学习资料就是在MDN,没有之一。

    傅_hc
  • Mysql 能像 MongoDB 那样存储文档了

    Mysql 5.7.12 做出了一项重大改进:支持文档型存储,意味着在 Mysql 中就可以像 MongoDB 那样存储 JSON document 了 这项改...

    dys
  • Kafka单机部署

    kafka是由Apache软件基金会发布的一个开源流处理平台,由Scala和Java编写。它是一种高吞吐量的分布式发布的订阅消息系统,它可以处理消费者规模的网站...

    小手冰凉
  • Flink Connector 深度解析

    作者介绍:董亭亭,快手大数据架构实时计算引擎团队负责人。目前负责 Flink 引擎在快手内的研发、应用以及周边子系统建设。2013 年毕业于大连理工大学,曾就职...

    zhisheng

扫码关注云+社区

领取腾讯云代金券