zoukankan      html  css  js  c++  java
  • 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 ", 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 ", 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 ", 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 ",
    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 ",
    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 ", 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 ", 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 ");
    else
    #endif
    return sprintf(buf, "0 ");
    }
    static DEVICE_ATTR_RO(hctosys);
    只读属性,如果返回1代表系统的clock最近一次使用rtc设置。返回0代表没有。
    ---------------------
    作者:Loopers
    来源:CSDN
    原文:https://blog.csdn.net/longwang155069/article/details/52353408
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    夏季适合IT程序员的养生小妙招
    夏季适合IT程序员的养生小妙招
    JS实现分钟数和时间小时 格式的转换
    Linux入门基础(1)
    Linux入门基础(1)
    Linux入门基础(1)
    常见通信协议HTTP、TCP、UDP的简单介绍
    BMP彩色转成黑色二值图
    《暗时间》笔记
    L53-Maximum-Subarray
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/10509147.html
Copyright © 2011-2022 走看看