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

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代表没有。

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值