程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-RTC驱动

----------------------------------------------------------------------------------------------------------------------------

内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

 一、RTC框架

1.1 RTC概述

RTC,英文全称Real Time Clock,中文就是实时时钟,是一个可以为系统提供精确的时间基准的元器件,主要是用来计时,产生闹钟等。

RTC一般可以使用备份电池供电,所以即使设备关机掉电,RTC也能在备份电池的供电下继续正常计时,这样在每次系统开机上电时就可以从RTC设备中读取到准确的时间。

RTC时间在每次系统启动的时候会使用,在需要的时候也可以由系统将要设置的时间写入到RTC设备。

在linux系统中,RTC可以使用周期性的中断来产生闹钟,也可以在系统suspend的时候作为系统的唤醒源使用。

1.2 RTC框架

Mini2440裸机开发之RTC小节中我们已经介绍了RTC裸机程序的编写。而在linux中,内核设计了一套RTC框架,如果想增加某一种新RTC硬件的驱动,只需要去编写芯片相关的代码,然后调用内核提供函数注册到RTC核心层即可。

 

RTC框架主要由以下部分组成:

  • hardware:提供时间设置,通过一定的接口(比如通过I2C外接hym8563芯片,或者SoC片内集成了RTC)和RTC驱动进行通信;
  • driver:完成RTC硬件的访问功能,提供访问接口,以驱动的方式注册到内核;
  • class.c:这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口;
  • interface.c:屏蔽硬件相关的细节,提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数;
  • rtc-lib.c:提供通用的时间操作函数,如rtc_time_to_tm,rtc_valid_tm;
  • rtc-dev.c:创建字符设备节点,即在/dev/目录下创建设备节点供应用层访问,如open、read、ioctl等,访问方式填充到file_operations结构体中;
  • hctosys.c:将硬件时钟写给 wall time;
  • rtc-sysfs.c:与sysfs有关;
  • rtc-proc.c:与proc文件系统有关;

1.3 目录结构

linux内核将RTC驱动相关的代码放在drivers/rtc目录下,这下面的文件还是比较多的,我们大概了解一下即可。

除了我们上面介绍的那些文件外,以rtc-xxx命名的大部分都是各个平台的RTC驱动,比如rtc-s3c.c、rtc-hym8563.c。

二、RTC核心数据结构

学习RTC驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

2.1 struct rtc_device

linux内核使用struct rtc_device数据结构来描述一个rtc设备,rtc_device包含了字符设备,rtc设备操作集,中断等信息,定义在include/linux/rtc.h:

struct rtc_device {
        struct device dev;
        struct module *owner;

        int id;

        const struct rtc_class_ops *ops;
        struct mutex ops_lock;

        struct cdev char_dev;
        unsigned long flags;

        unsigned long irq_data;
        spinlock_t irq_lock;
        wait_queue_head_t irq_queue;  // 等待队列头
        struct fasync_struct *async_queue;

        int irq_freq;
        int max_user_freq;

        struct timerqueue_head timerqueue;
        struct rtc_timer aie_timer;
        struct rtc_timer uie_rtctimer;
        struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */
        int pie_enabled;
        struct work_struct irqwork;
        /* Some hardware can't support UIE mode */
        int uie_unsupported;

        /* Number of nsec it takes to set the RTC clock. This influences when
         * the set ops are called. An offset:
         *   - of 0.5 s will call RTC set for wall clock time 10.0 s at 9.5 s
         *   - of 1.5 s will call RTC set for wall clock time 10.0 s at 8.5 s
         *   - of -0.5 s will call RTC set for wall clock time 10.0 s at 10.5 s
         */
        long set_offset_nsec;

        bool registered;

        /* Old ABI support */
        bool nvram_old_abi;
        struct bin_attribute *nvram;

        time64_t range_min;
        timeu64_t range_max;
        time64_t start_secs;
        time64_t offset_secs;
        bool set_start_time;

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
        struct work_struct uie_task;
        struct timer_list uie_timer;
        /* Those fields are protected by rtc->irq_lock */
        unsigned int oldsecs;
        unsigned int uie_irq_active:1;
        unsigned int stop_uie_polling:1;
        unsigned int uie_task_active:1;
        unsigned int uie_timer_active:1;
#endif
};

其中部分参数含义如下:

  • dev:设备驱动模型中的device,可以将rtc_device看做其子类;
  • owner:模块的拥有者;
  • id:rtc设备编号;
  • ops:rtc设备操作函数集;
  • ops_lock:互斥锁,解决对rtc设备并发访问问题;
  • char_dev:rtc字符设备;
  • flags:rtc的状态标志,比如RTC_DEV_BUSY;
  • irq_data:irq中断数据;
  • irq_lock:自旋锁,互斥访问irq中断数据;
  • irq_queue:数据查询时用到的rtc等待队列头;头节点为struct wait_queue_head_t类型,等待队列中的元素为struct wait_queue_t类型;
  • async_queue:异步队列;
  • irq_freq:rtc中断频率,实际上就是时钟节拍中断的频率;
  • max_user_freq:rtc时钟节拍中断最大频率;
  • irqwork:该工作会在rtc中断函数中执行,工作函数会被设置为rtc_timer_do_work;
  • timerqueue:定时器队列,使用最小堆算法实现,根节点保存的是最先触发的定时器;struct timerqueue_head类型;
  • aie_timer:闹钟定时器,保存闹钟时间,闹钟时间到时执行定时器超时处理函数rtc_aie_update_irq;struct rtc_timer类型;
  • uie_rtctimer:更新定时器,定时器超时处理函数被设置为rtc_uie_update_irq;struct rtc_timer类型;
  • pie_timer:周期高精度定时器,定时器超时处理函数设置为rtc_pie_update_irq;struct hrtimer类型;
  • pie_enabled:周期中断使能标志; 
  • range_int:最小秒数;用来描述rtc设备支持的最小时间;
  • range_max:最大秒数;用来描述rtc设备支持的最大时间;
  • start_secs:开始秒数;rtc设备设置的起始时间;
  • offset_secs:时间偏移秒数;RTC读取到的时间+偏移秒数就是真实的时间;
  • uie_task:该工作会在uie_timer定时器超时函数rtc_uie_timer中执行,工作函数会被设置为rtc_uie_task;
  • uie_timer:更新定时器,定时器超时函数会被设置为rtc_uie_timer;struct timer_list类型;

range_int、range_max、start_secs、offset_secs这几个参数是做时间映射使用的,比如我们之前介绍的S3C2440中的RTC能够表示的年数范围是00~99,这种情况下我们想将时间映射到2000~2099年的范围,就需要做一个时间区域的映射,比如加上2000年的偏移。

我们从rtc设备读取到最小时间为00-01-01 00:00:00,而我们想表示2000-01-01 00:00:00~2100-01-01 00:00:00这段时间,那么我们就可以将:

  • start_secs:设置为时间00-01-01 00:00:00对应的秒数;
  • range_int:设置为时间2000-01-01 00:00:00对应的秒数;
  • range_max:设置为时间2100-01-01 00:00:00对应的秒数;
  • offset_secs:时间区域映射的偏移量;

当然,对于有些rtc设备,它自身能够表示2000~2999年的范围,就没必要做这个应映射了。

rtc_device结构体屏蔽了不同RTC硬件之间的差异,通过rtc_class_ops结构体为上层提供了访问硬件设备的统一接口,该结构体中包含了对硬件操作的相关函数。

2.2 struct rtc_class_ops

考虑到RTC物理设备可能采用不同的接线方式,比如I2C、平台、SPI等,linux内核使用struct rtc_class_ops数据结构来描述如何对rtc设备进行操作,定义在include/linux/rtc.h:

/*
 * For these RTC methods the device parameter is the physical device
 * on whatever bus holds the hardware (I2C, Platform, SPI, etc), which
 * was passed to rtc_device_register().  Its driver_data normally holds
 * device state, including the rtc_device pointer for the RTC.
 *
 * Most of these methods are called with rtc_device.ops_lock held,
 * through the rtc_*(struct rtc_device *, ...) calls.
 *
 * The (current) exceptions are mostly filesystem hooks:
 *   - the proc() hook for procfs
 *   - non-ioctl() chardev hooks:  open(), release()
 *
 * REVISIT those periodic irq calls *do* have ops_lock when they're
 * issued through ioctl() ...
 */
struct rtc_class_ops {
        int (*ioctl)(struct device *, unsigned int, unsigned long);
        int (*read_time)(struct device *, struct rtc_time *);
        int (*set_time)(struct device *, struct rtc_time *);
        int (*read_alarm)(struct device *, struct rtc_wkalrm *);
        int (*set_alarm)(struct device *, struct rtc_wkalrm *);
        int (*proc)(struct device *, struct seq_file *);
        int (*alarm_irq_enable)(struct device *, unsigned int enabled);
        int (*read_offset)(struct device *, long *offset);
        int (*set_offset)(struct device *, long offset);
};

其中部分参数含义如下:

  • ioctl:io控制函数,用来实现各种控制命令;
  • read_time:读取时间;
  • set_time:设置时间;
  • read_alarm:读取闹钟;
  • set_alarm:设置闹钟;
  • proc:procfs接口;
  • alarm_irq_enable:闹钟中断使能;
  • read_offset:
  • set_offset:

2.3 struct rtc_time

struct rtc_time用于表示时间,定义在include/uapi/linux/rtc.h:

/*
 * The struct used to pass data via the following ioctl. Similar to the
 * struct tm in <time.h>, but it needs to be here so that the kernel
 * source is self contained, allowing cross-compiles, etc. etc.
 */

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;
};

其中参数含义如下:

  • tm_sec:秒数;
  • tm_min:分钟数;
  • hour:小时数;
  • mday:天;
  • mon:月份;
  • year:年;
  • tm_wday:一周中的某一天;
  • tm_year:以1900年为基准,偏移年份;
  • tm_yday:一年中的某一天;

2.4 struct rtc_wkalrm

struct rtc_wkalrm用于表示唤醒闹钟,定义在include/uapi/linux/rtc.h:

/*
 * 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 */
};

重点需要关注的是enabled,只有为1时才表示开启闹钟,闹钟开启才会触发INT_RTC闹钟中断,否则不会触发INT_RTC闹钟中断。

2.5 struct rtc_timer

struct rtc_timer定义在include/linux/rtc.h:

struct rtc_timer {
        struct timerqueue_node node;  
        ktime_t period;    // 定时器触发周期,多久触发一次,比如1小时
        void (*func)(struct rtc_device *rtc);   // 定时器超时处理函数
        struct rtc_device *rtc;  
        int enabled;       // 定时器开关使能
};

这里有一个重要的成员node,其类型为struct timerqueue_node,RTC框架中通过struct timerqueue_node和struct timerqueue实现了最小堆定时器。

关于最小堆的概念这里就不详细介绍了,其本质就是一个完全二叉树,只不过满足以下要求:

  • 其中任意非叶子节点数值均不大于其左子节点和右子节点的值;

也就是说最小堆的根节点保存着整个完全二叉树的最小元素。因此我们可以通过堆排序算法来对一组数字进行排序。时间复杂度为$O(nlogn)$。

2.5.1 struct timerqueue_node

struct timerqueue_node定义在include/linux/timerqueue.h文件,用于表示定时器队列中的节点,expires保存着绝对时间:

struct timerqueue_node {
        struct rb_node node;
        ktime_t expires;
};

还是内核一贯的风格, struct timerqueue_node 还是作为一个成员在需要排序的对象中,然后使用 container_of计算被浸入的对象的指针。

2.5.2 struct timerqueue_head

struct timerqueue_head定义在include/linux/timerqueue.h文件,用于表示定时器队列头:

struct timerqueue_head {
        struct rb_root head;
        struct timerqueue_node *next;
};

其next字段指向最小堆的第一个元素,每次插入新的struct timerqueue_node可能被更新(每次新插入一个节点,都会重新构造最小堆)。

2.5.3 相关API

通过timerqueue_init_head初始化定时器队列头:

static inline void timerqueue_init_head(struct timerqueue_head *head)
{
        head->head = RB_ROOT;
        head->next = NULL;
}

通过timerqueue_add向最小堆中插入一个元素,返回true表示插入成功;每新增一个节点,都会重新更新最小堆,将插入的元素调整到正确的位置:

bool timerqueue_add(struct timerqueue_head *head, struct timerqueue_node *node)

通过timerqueue_del从最小堆中移除指定的元素,返回true表示移除成功;每移除一个节点,都会重新更新最小堆;

bool timerqueue_del(struct timerqueue_head *head,
                           struct timerqueue_node *node);

通过timerqueue_getnext获取最小堆的根节点,也就是定时器过期时间最早的节点:

/**
 * timerqueue_getnext - Returns the timer with the earliest expiration time
 *
 * @head: head of timerqueue
 *
 * Returns a pointer to the timer node that has the
 * earliest expiration time.
 */
static inline
struct timerqueue_node *timerqueue_getnext(struct timerqueue_head *head)
{
        return head->next;
}

三、RTC驱动API

linux内核提供了一组函数用于操作rtc_device结构体。

3.1 注册rtc设备(rtc_register_device)

rtc_register_device用于注册RTC设备,定义在include/linux/rtc.h:

#define rtc_register_device(device) \
        __rtc_register_device(THIS_MODULE, device)

其入参device一般为平台设备的dev成员。该函数内部调用了__rtc_register_device,位于drivers/rtc/class.c:

int __rtc_register_device(struct module *owner, struct rtc_device *rtc)
{
        struct rtc_wkalrm alrm;
        int err;

        if (!rtc->ops)
                return -EINVAL;

        rtc->owner = owner;
        rtc_device_get_offset(rtc);

        /* Check to see if there is an ALARM already set in hw */
        err = __rtc_read_alarm(rtc, &alrm);  // 该函数主要是调用rtc->ops->read_alarm(即s3c_rtc_getalarm)获取闹钟时间并保存在alarm参数中,同时会读取RTCALM寄存器的全局闹钟使能位的值并赋值给alarm->enabled ;
        if (!err && !rtc_valid_tm(&alrm.time))  // 如果闹钟时间有效,进入该分支
                rtc_initialize_alarm(rtc, &alrm);

        rtc_dev_prepare(rtc);    // 字符设备初始化

        err = cdev_device_add(&rtc->char_dev, &rtc->dev);
        if (err)
                dev_warn(rtc->dev.parent, "failed to add char device %d:%d\n",
                         MAJOR(rtc->dev.devt), rtc->id);
        else
                dev_dbg(rtc->dev.parent, "char device (%d:%d)\n",
                        MAJOR(rtc->dev.devt), rtc->id);

        rtc_proc_add_device(rtc);

        rtc->registered = true;
        dev_info(rtc->dev.parent, "registered as %s\n",
                 dev_name(&rtc->dev));

        return 0;
}

函数主要流程如下:

  • 调用rtc_device_get_offset根据start_secs、range_min、range_max去计算偏移秒数offset_secs:
  • 调用__rtc_read_alarm读取rtc闹钟寄存器,获取闹钟时间;
    • 调用rtc_initialize_alarm函数,如果闹钟开启(即alarm->enabled为1),则会执行timerqueue_add(&rtc->timerqueue, &rtc->aie_timer.node),这里是将rtc->aie_timer.node节点添加到rtc->timerqueue队列中;实际上在注册RTC设备时,我们还没有开启闹钟(即alarm->enabled为0),因此并将aie_timer定时器添加到定时器队列中;
  • 调用rtc_dev_prepare初始化字符设备,设置主设备号为MAJOR(rtc_devt),次设备号为rtc->id,设置字符设备的文件操作集为rtc_dev_fops;
  • 调用cdev_device_add注册字符设备;
    • 其内部先是调用了cdev_add(cdev, dev->devt, 1)将1个字符设备添加到内核;
    • 然后调用device_add(dev)注册dev设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/rtc(n);
  • 调用rtc_proc_add_device创建proc文件系统接口;
3.1.1 rtc_device_get_offset

rtc_device_get_offset定义在include/linux/rtc.h,函数主要是根据start_secs、range_min、range_max去计算偏移秒数offset_secs;

static void rtc_device_get_offset(struct rtc_device *rtc)
{
        time64_t range_secs;
        u32 start_year;
        int ret;

        /*
         * If RTC driver did not implement the range of RTC hardware device,
         * then we can not expand the RTC range by adding or subtracting one
         * offset.
         */
        if (rtc->range_min == rtc->range_max)
                return;

        ret = device_property_read_u32(rtc->dev.parent, "start-year",  // 获取基址年份
                                       &start_year);
        if (!ret) {
                rtc->start_secs = mktime64(start_year, 1, 1, 0, 0, 0);
                rtc->set_start_time = true;
        }

        /*
         * If user did not implement the start time for RTC driver, then no
         * need to expand the RTC range.
         */
        if (!rtc->set_start_time)
                return;

        range_secs = rtc->range_max - rtc->range_min + 1;

        /*
         * If the start_secs is larger than the maximum seconds (rtc->range_max)
         * supported by RTC hardware or the maximum seconds of new expanded
         * range (start_secs + rtc->range_max - rtc->range_min) is less than
         * rtc->range_min, which means the minimum seconds (rtc->range_min) of
         * RTC hardware will be mapped to start_secs by adding one offset, so
         * the offset seconds calculation formula should be:
         * rtc->offset_secs = rtc->start_secs - rtc->range_min;
         *
         * If the start_secs is larger than the minimum seconds (rtc->range_min)
         * supported by RTC hardware, then there is one region is overlapped
         * between the original RTC hardware range and the new expanded range,
         * and this overlapped region do not need to be mapped into the new
         * expanded range due to it is valid for RTC device. So the minimum
         * seconds of RTC hardware (rtc->range_min) should be mapped to
         * rtc->range_max + 1, then the offset seconds formula should be:
         * rtc->offset_secs = rtc->range_max - rtc->range_min + 1;
         *
         * If the start_secs is less than the minimum seconds (rtc->range_min),
         * which is similar to case 2. So the start_secs should be mapped to
         * start_secs + rtc->range_max - rtc->range_min + 1, then the
         * offset seconds formula should be:
         * rtc->offset_secs = -(rtc->range_max - rtc->range_min + 1);
         *
         * Otherwise the offset seconds should be 0.
         */
        if (rtc->start_secs > rtc->range_max ||
            rtc->start_secs + range_secs - 1 < rtc->range_min)
                rtc->offset_secs = rtc->start_secs - rtc->range_min;
        else if (rtc->start_secs > rtc->range_min) 
rtc
->offset_secs = range_secs; else if (rtc->start_secs < rtc->range_min) rtc->offset_secs = -range_secs; else rtc->offset_secs = 0; }
3.1.2 __rtc_read_alarm

__rtc_read_alarm定义在drivers/rtc/interface.c,该函数主要是调用rtc->ops->read_alarm(即s3c_rtc_getalarm)获取闹钟时间并保存在alarm参数中,同时会读取RTCALM寄存器的全局闹钟使能位的值并赋值给alarm->enabled ;

如果获取到的闹钟时间中的时、分、秒、年、月、日存在错误,还会进行一个校正工作;

int __rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
        int err;
        struct rtc_time before, now;
        int first_time = 1;
        time64_t t_now, t_alm;
        enum { none, day, month, year } missing = none;
        unsigned int days;

        /* The lower level RTC driver may return -1 in some fields,
         * creating invalid alarm->time values, for reasons like:
         *
         *   - The hardware may not be capable of filling them in;
         *     many alarms match only on time-of-day fields, not
         *     day/month/year calendar data.
         *
         *   - Some hardware uses illegal values as "wildcard" match
         *     values, which non-Linux firmware (like a BIOS) may try
         *     to set up as e.g. "alarm 15 minutes after each hour".
         *     Linux uses only oneshot alarms.
         *
         * When we see that here, we deal with it by using values from
         * a current RTC timestamp for any missing (-1) values.  The
         * RTC driver prevents "periodic alarm" modes.
         *
         * But this can be racey, because some fields of the RTC timestamp
         * may have wrapped in the interval since we read the RTC alarm,
         * which would lead to us inserting inconsistent values in place
         * of the -1 fields.
         *
         * Reading the alarm and timestamp in the reverse sequence
         * would have the same race condition, and not solve the issue.
         *
         * So, we must first read the RTC timestamp,
         * then read the RTC alarm value,
         * and then read a second RTC timestamp.
         *
         * If any fields of the second timestamp have changed
         * when compared with the first timestamp, then we know
         * our timestamp may be inconsistent with that used by
         * the low-level rtc_read_alarm_internal() function.
         *
         * So, when the two timestamps disagree, we just loop and do
         * the process again to get a fully consistent set of values.
         *
         * This could all instead be done in the lower level driver,
         * but since more than one lower level RTC implementation needs it,
         * then it's probably best best to do it here instead of there..
         */

        /* Get the "before" timestamp */
        err = rtc_read_time(rtc, &before);               // 调用rtc->ops->read_time获取rtc时间
       if (err < 0)
                return err;
        do {
                if (!first_time)
                        memcpy(&before, &now, sizeof(struct rtc_time));
                first_time = 0;

                /* get the RTC alarm values, which may be incomplete */
                err = rtc_read_alarm_internal(rtc, alarm);  / 调用rtc->ops->read_alarm获取rtc闹钟时间
                if (err)
                        return err;

                /* full-function RTCs won't have such missing fields */
                if (rtc_valid_tm(&alarm->time) == 0) {      // 时间校验通过
                        rtc_add_offset(rtc, &alarm->time);  // 加上偏移时间,得到真正的时间
                        return 0;
                }

                /* get the "after" timestamp, to detect wrapped fields */
                err = rtc_read_time(rtc, &now);   // 调用rtc->ops->read_time从rtc时间
                if (err < 0)
                        return err;

                /* note that tm_sec is a "don't care" value here: */
        } while (before.tm_min  != now.tm_min ||
                 before.tm_hour != now.tm_hour ||
                 before.tm_mon  != now.tm_mon ||
                 before.tm_year != now.tm_year);

        /* Fill in the missing alarm fields using the timestamp; we
         * know there's at least one since alarm->time is invalid.
         */
        if (alarm->time.tm_sec == -1)
                alarm->time.tm_sec = now.tm_sec;
        if (alarm->time.tm_min == -1)
                alarm->time.tm_min = now.tm_min;
        if (alarm->time.tm_hour == -1)
                alarm->time.tm_hour = now.tm_hour;

        /* For simplicity, only support date rollover for now */
        if (alarm->time.tm_mday < 1 || alarm->time.tm_mday > 31) {
                alarm->time.tm_mday = now.tm_mday;
                missing = day;
        }
        if ((unsigned int)alarm->time.tm_mon >= 12) {
                alarm->time.tm_mon = now.tm_mon;
                if (missing == none)
                        missing = month;
        }
        if (alarm->time.tm_year == -1) {
                alarm->time.tm_year = now.tm_year;
                if (missing == none)
                        missing = year;
        }

        /* Can't proceed if alarm is still invalid after replacing
         * missing fields.
         */
        err = rtc_valid_tm(&alarm->time);
        if (err)
                goto done;

        switch (missing) {
        /* 24 hour rollover ... if it's now 10am Monday, an alarm that
         * that will trigger at 5am will do so at 5am Tuesday, which
         * could also be in the next month or year.  This is a common
         * case, especially for PCs.
         */
        case day:
                dev_dbg(&rtc->dev, "alarm rollover: %s\n", "day");
                t_alm += 24 * 60 * 60;
                rtc_time64_to_tm(t_alm, &alarm->time);
                break;

        /* Month rollover ... if it's the 31th, an alarm on the 3rd will
         * be next month.  An alarm matching on the 30th, 29th, or 28th
         * may end up in the month after that!  Many newer PCs support
         * this type of alarm.
         */
        case month:
                dev_dbg(&rtc->dev, "alarm rollover: %s\n", "month");
                do {
                        if (alarm->time.tm_mon < 11) {
                                alarm->time.tm_mon++;
                        } else {
                                alarm->time.tm_mon = 0;
                                alarm->time.tm_year++;
                        }
                        days = rtc_month_days(alarm->time.tm_mon,
                                              alarm->time.tm_year);
                } while (days < alarm->time.tm_mday);
                break;

        /* Year rollover ... easy except for leap years! */
        case year:
                dev_dbg(&rtc->dev, "alarm rollover: %s\n", "year");
                do {
                        alarm->time.tm_year++;
                } while (!is_leap_year(alarm->time.tm_year + 1900) &&
                         rtc_valid_tm(&alarm->time) != 0);
                break;

        default:
                dev_warn(&rtc->dev, "alarm rollover not handled\n");
        }

        err = rtc_valid_tm(&alarm->time);

done:
        if (err)
                dev_warn(&rtc->dev, "invalid alarm value: %ptR\n",
                         &alarm->time);

        return err;
}

rtc_read_alarm_internal最终调用的rtc->ops->read_alarm,即s3c_rtc_getalarm;这里通过读取闹钟寄存器获取闹钟时间;

static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
        struct s3c_rtc *info = dev_get_drvdata(dev);
        struct rtc_time *alm_tm = &alrm->time;
        unsigned int alm_en;
        int ret;

        ret = s3c_rtc_enable_clk(info);
        if (ret)
                return ret;

        alm_tm->tm_sec  = readb(info->base + S3C2410_ALMSEC);        // 读取闹钟秒数据寄存器
        alm_tm->tm_min  = readb(info->base + S3C2410_ALMMIN);        // 读取闹钟分数据寄存器 
        alm_tm->tm_hour = readb(info->base + S3C2410_ALMHOUR);
        alm_tm->tm_mon  = readb(info->base + S3C2410_ALMMON);
        alm_tm->tm_mday = readb(info->base + S3C2410_ALMDATE);
        alm_tm->tm_year = readb(info->base + S3C2410_ALMYEAR);

        alm_en = readb(info->base + S3C2410_RTCALM);                // 读取闹钟控制寄存器的值

        s3c_rtc_disable_clk(info);

        alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0;   // 根据RTCALM寄存器的全局闹钟使能位来设置报警状态结构rtc_wkalrm

        dev_dbg(dev, "read alarm %d, %ptR\n", alm_en, alm_tm);

        /* decode the alarm enable field */
        if (alm_en & S3C2410_RTCALM_SECEN)                       // 如果RTCALM寄存器的秒闹钟使能,则将rtc_wkalrm中存放的秒数据由BCD格式转换为bin格式,否则设置为0xff
                alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec);

        if (alm_en & S3C2410_RTCALM_MINEN)                        // 分闹钟使能 
                alm_tm->tm_min = bcd2bin(alm_tm->tm_min);

        if (alm_en & S3C2410_RTCALM_HOUREN)                       // 时闹钟使能
                alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour);

        if (alm_en & S3C2410_RTCALM_DAYEN)                        // 日闹钟使能 
                alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday);

        if (alm_en & S3C2410_RTCALM_MONEN) {                      // 月闹钟使能
                alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon);
                alm_tm->tm_mon -= 1;
        }

        if (alm_en & S3C2410_RTCALM_YEAREN)                        // 年闹钟使能
                alm_tm->tm_year = bcd2bin(alm_tm->tm_year);

        return 0;
}
View Code
3.1.3 rtc_initialize_alarm

rtc_initialize_alarm函数定义在drivers/rtc/interface.c,这个函数主要的作用就是在闹钟开启的前提下,向定时器队列中加入闹钟定时器aie_timer:

/* Called once per device from rtc_device_register */
int rtc_initialize_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
        int err;
        struct rtc_time now;

        err = rtc_valid_tm(&alarm->time);   // 闹钟时间合法 
        if (err != 0)
                return err;

        err = rtc_read_time(rtc, &now);     // 读取当前时间
        if (err)
                return err;

        err = mutex_lock_interruptible(&rtc->ops_lock);
        if (err)
                return err;

        rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);        // 设置闹钟定时器过期时间
        rtc->aie_timer.period = 0;

        /* Alarm has to be enabled & in the future for us to enqueue it */
        if (alarm->enabled && (rtc_tm_to_ktime(now) <                   // 闹钟开启,并且闹钟时间未到,实际不会进入
                         rtc->aie_timer.node.expires)) {
                rtc->aie_timer.enabled = 1;                              // 使能标志位 
                timerqueue_add(&rtc->timerqueue, &rtc->aie_timer.node);  // 将闹钟定时器添加到定时器队列
                trace_rtc_timer_enqueue(&rtc->aie_timer);
        }
        mutex_unlock(&rtc->ops_lock);
        return err;
}
3.1.4 rtc_dev_prepare

rtc_dev_prepare定义在drivers/rtc/dev.c,该函数主要做了如下操作:初始化rtc->dev,设置其主设备号为MAJOR(rtc_devt),这个是在模块入口函数rtc_dev_init中申请的,次设备号为rtc->id;设置字符设备rtc->char_dev的文件操作集为rtc_dev_fops;

/* insertion/removal hooks */

void rtc_dev_prepare(struct rtc_device *rtc)
{
        if (!rtc_devt)
                return;

        if (rtc->id >= RTC_DEV_MAX) {
                dev_dbg(&rtc->dev, "too many RTC devices\n");
                return;
        }

        rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);   // 设置dev设备号

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL   // 未配置
        INIT_WORK(&rtc->uie_task, rtc_uie_task);           // 初始化工作,工作函数设置为rtc_uie_task
        timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);    // 初始化定时器,并设置定时器超时处理函数为rtc_uie_timer
#endif

        cdev_init(&rtc->char_dev, &rtc_dev_fops);
        rtc->char_dev.owner = rtc->owner;
}
3.1.5 cdev_device_add

cdev_device_add定义在fs/char_dev.c文件,这个函数没什么好说的,太熟悉不过了:

/**
 * cdev_device_add() - add a char device and it's corresponding
 *      struct device, linkink
 * @dev: the device structure
 * @cdev: the cdev structure
 *
 * cdev_device_add() adds the char device represented by @cdev to the system,
 * just as cdev_add does. It then adds @dev to the system using device_add
 * The dev_t for the char device will be taken from the struct device which
 * needs to be initialized first. This helper function correctly takes a
 * reference to the parent device so the parent will not get released until
 * all references to the cdev are released.
 *
 * This helper uses dev->devt for the device number. If it is not set
 * it will not add the cdev and it will be equivalent to device_add.
 *
 * This function should be used whenever the struct cdev and the
 * struct device are members of the same structure whose lifetime is
 * managed by the struct device.
 *
 * NOTE: Callers must assume that userspace was able to open the cdev and
 * can call cdev fops callbacks at any time, even if this function fails.
 */
int cdev_device_add(struct cdev *cdev, struct device *dev)
{
        int rc = 0;

        if (dev->devt) {
                cdev_set_parent(cdev, &dev->kobj);

                rc = cdev_add(cdev, dev->devt, 1);
                if (rc)
                        return rc;
        }

        rc = device_add(dev);
        if (rc)
                cdev_del(cdev);

        return rc;
}
3.1.6 rtc_proc_add_device

rtc_proc_add_device函数定义在drivers/rtc/proc.c:

static bool is_rtc_hctosys(struct rtc_device *rtc)
{
        return (rtc->id == 0);
}


void rtc_proc_add_device(struct rtc_device *rtc)
{
        if (is_rtc_hctosys(rtc))
                proc_create_single_data("driver/rtc", 0, NULL, rtc_proc_show,
                                        rtc);
}

主要调用了proc_create_sinagle_data。proc_create_sinagle_data完成创建文件节点的作用,并将文件的操作函数与节点联系起来。调用这个函数后,在/proc/driver目录下就会有一个文件rtc。

3.2 注册rtc设备(rdevm_rtc_device_register)

相比于__rtc_register_device函数rdevm_rtc_device_register是一个带有资源管理的RTC设备注册函数:

/**
 * devm_rtc_device_register - resource managed rtc_device_register()
 * @dev: the device to register
 * @name: the name of the device (unused)
 * @ops: the rtc operations structure
 * @owner: the module owner
 *
 * @return a struct rtc on success, or an ERR_PTR on error
 *
 * Managed rtc_device_register(). The rtc_device returned from this function
 * are automatically freed on driver detach.
 * This function is deprecated, use devm_rtc_allocate_device and
 * rtc_register_device instead
 */
struct rtc_device *devm_rtc_device_register(struct device *dev,                // 平台设备的dev成员 
                                            const char *name,                  // "s3c" 
                                            const struct rtc_class_ops *ops,   // 参数为&s3c_rtcops
                                            struct module *owner)
{
        struct rtc_device *rtc;
        int err;

        rtc = devm_rtc_allocate_device(dev);
        if (IS_ERR(rtc))
                return rtc;

        rtc->ops = ops;    // 设置rtc设备操作函数集

        err = __rtc_register_device(owner, rtc);
        if (err)
                return ERR_PTR(err);

        return rtc;
}

其内部先是调用了devm_rtc_allocate_device 去申请一个rtc_device,然后调用__rtc_register_device。

3.2.1 devm_rtc_allocate_device 

devm_rtc_allocate_device函数的主要作用就是分配一个rtc_device,同时并进行初始化工作:

struct rtc_device *devm_rtc_allocate_device(struct device *dev)
{
        struct rtc_device **ptr, *rtc;
        int id, err;

        id = rtc_device_get_id(dev);        // 从rtc_ida获取一个未使用的编号
        if (id < 0)
                return ERR_PTR(id);

        ptr = devres_alloc(devm_rtc_release_device, sizeof(*ptr), GFP_KERNEL);   // 动态申请rtc_device指针
        if (!ptr) {
                err = -ENOMEM;
                goto exit_ida;
        }

        rtc = rtc_allocate_device();  // 动态申请rtc_device
        if (!rtc) {
                err = -ENOMEM;
                goto exit_devres;
        }

        *ptr = rtc;
        devres_add(dev, ptr);

        rtc->id = id;                               // 设置rtc设备编号
        rtc->dev.parent = dev;                      // 设置父设备为platform设备的device
        dev_set_name(&rtc->dev, "rtc%d", id);       // 设置dev名称为rtc%d

        return rtc;

exit_devres:
        devres_free(ptr);
exit_ida:
        ida_simple_remove(&rtc_ida, id);
        return ERR_PTR(err);
}
3.2.2 rtc_allocate_device

rtc_allocate_device函数用于动态申请rtc_device,并进行初始化工作:

/* Ensure the caller will set the id before releasing the device */
static struct rtc_device *rtc_allocate_device(void)
{
        struct rtc_device *rtc;

        rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
        if (!rtc)
                return NULL;

        device_initialize(&rtc->dev);

        /* Drivers can revise this default after allocating the device. */
        rtc->set_offset_nsec =  NSEC_PER_SEC / 2;

        rtc->irq_freq = 1;               // rtc中断频率
        rtc->max_user_freq = 64;          
        rtc->dev.class = rtc_class;      // 设置其类为rtc_class,从而当注册rtc->dev设备后,会在/sys/class/rtc下创建以rtc设备名命名的文件夹
        rtc->dev.groups = rtc_get_dev_attribute_groups();
        rtc->dev.release = rtc_device_release;

        mutex_init(&rtc->ops_lock);
        spin_lock_init(&rtc->irq_lock);
        init_waitqueue_head(&rtc->irq_queue);       // 初始化等待队列头

        /* Init timerqueue */
        timerqueue_init_head(&rtc->timerqueue);          // 初始化定时器队列头
        INIT_WORK(&rtc->irqwork, rtc_timer_do_work);     // 初始化中断工作,工作函数设置为rtc_timer_do_work
        /* Init aie timer */
        rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, rtc);      // 初始化闹钟定时器,设置定时器超时处理函数
        /* Init uie timer */
        rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, rtc);   // 初始化更新定时器,设置定时器超时处理函数
        /* Init pie timer */
        hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); // 初始化周期高精度定时器,并设置定时器超时处理函数
        rtc->pie_timer.function = rtc_pie_update_irq;
        rtc->pie_enabled = 0;

        return rtc;
} 

3.3 rtc_init

rtc_init为class.c文件模块入口函数:

static int __init rtc_init(void)
{
        rtc_class = class_create(THIS_MODULE, "rtc");
        if (IS_ERR(rtc_class)) {
                pr_err("couldn't create class\n");
                return PTR_ERR(rtc_class);
        }
        rtc_class->pm = RTC_CLASS_DEV_PM_OPS;
        rtc_dev_init();
        return 0;
}
subsys_initcall(rtc_init);

该函数主要做一下RTC驱动注册之前的初始化工作:

  • 创建名称为rtc的class;
  • 调用rtc_dev_init,动态申请/dev/rtcN的设备号;
3.3.1 rtc_dev_init

rtc_dev_init函数定义在drivers/rtc/dev.c文件,该函数动态申请设备号,保存到rtc_devt中,次设备号的起始为0, 申请次设备号的个数16。

void __init rtc_dev_init(void)
{
        int err;

        err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
        if (err < 0)
                pr_err("failed to allocate char dev region\n");
}

四、RTC驱动

这里以s3c24xx芯片外设RTC为例,一步步推出内核中RTC设备驱动是如何实现的。

RTC设备驱动程序存放在内核源码树的drivers/rtc目录,s3c24xx芯片的RTC驱动程序对应的是rtc-s3c.c文件。

4.1 模块入口函数

4.1.1 platform驱动注册

下面我们就从这个文件中的入口函数进行分析:

static const struct of_device_id s3c_rtc_dt_match[] = {  // 存储设备驱动程序和设备节点之间得匹配信息
        {
                .compatible = "samsung,s3c2410-rtc",
                .data = &s3c2410_rtc_data,
        }, {
                .compatible = "samsung,s3c2416-rtc",
                .data = &s3c2416_rtc_data,
        }, {
                .compatible = "samsung,s3c2443-rtc",
                .data = &s3c2443_rtc_data,
        }, {
                .compatible = "samsung,s3c6410-rtc",
                .data = &s3c6410_rtc_data,
        }, {
                .compatible = "samsung,exynos3250-rtc",
                .data = &s3c6410_rtc_data,
        },
        { /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, s3c_rtc_dt_match);

static struct platform_driver s3c_rtc_driver = {
        .probe          = s3c_rtc_probe,     // rtc探测函数
        .remove         = s3c_rtc_remove,    // rtc移除函数  
        .driver         = {
                .name   = "s3c-rtc",
                .pm     = &s3c_rtc_pm_ops,
                .of_match_table = of_match_ptr(s3c_rtc_dt_match),
        },
};
module_platform_driver(s3c_rtc_driver);

module_platform_driver这个宏之前已经介绍过多次了,其展开后等价于:

static int __init s3c_rtc_driver_init(void) 
{ 
    return platform_driver_register(&(s3c_rtc_driver)); 
} 
module_init(s3c_rtc_driver_init); 
static void __exit s3c_rtc_driver_exit(void) 
{ 
    platform_driver_unregister(&(s3c_rtc_driver)); 
} 
module_exit(s3c_rtc_driver_exit);

看到这里是不是有点意外,这里是通过platform_driver_register函数注册了一个名称为"s3c-rtc"的platform驱动。

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备和platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是s3c_rtc_probe函数。

而platform设备和驱动匹配规则包括:

  • of_driver_match_device(dev, drv);这一种是进行设备树的匹配,这种方式我们先忽略;
  • strcmp(pdev->name, drv->name);这一种就是将platform设备的名称和platform驱动的名称进行匹配;

of_driver_match_device定义在include/linux/of_device.h:

/**
 * of_driver_match_device - Tell if a driver's of_match_table matches a device.
 * @drv: the device_driver structure to test
 * @dev: the device structure to match against
 */
static inline int of_driver_match_device(struct device *dev,
                                         const struct device_driver *drv)
{
        return of_match_device(drv->of_match_table, dev) != NULL;
}

可以看到这里是调用of_match_device函数,遍历device_driver的of_match_table数组,然后和device的of_node进行匹配,该函数定义在drivers/of/device.c:

/**
 * of_match_device - Tell if a struct device matches an of_device_id list
 * @matches: array of of device match structures to search in
 * @dev: the of device structure to match against
 *
 * Used by a driver to check whether an platform_device present in the
 * system is in its list of supported devices.
 */
const struct of_device_id *of_match_device(const struct of_device_id *matches,
                                           const struct device *dev)
{
        if ((!matches) || (!dev->of_node))
                return NULL;
        return of_match_node(matches, dev->of_node);
}
4.1.2 platform设备注册

而"s3c2410-rtc"的platform设备在arch/arm/plat-samsung/devs.c中定义,很显然名称并不是匹配的,因此我们需要修改名称为"s3c-rtc"才能和s3c_rtc_driver 中的name相同。

/* RTC */

#ifdef CONFIG_PLAT_S3C24XX
static struct resource s3c_rtc_resource[] = {
        [0] = DEFINE_RES_MEM(S3C24XX_PA_RTC, SZ_256),  // 0x57000000 RTC寄存器基地址
        [1] = DEFINE_RES_IRQ(IRQ_RTC),    // RTC闹钟中断
        [2] = DEFINE_RES_IRQ(IRQ_TICK),   // RTC时钟节拍中断 
};

struct platform_device s3c_device_rtc = {
        .name           = "s3c2410-rtc",
        .id             = -1,
        .num_resources  = ARRAY_SIZE(s3c_rtc_resource),   // 资源数量
        .resource       = s3c_rtc_resource,
};
#endif /* CONFIG_PLAT_S3C24XX */

此外我们需要修改arch/arm/mach-s3c24xx/mach-smdk2440.c文件:

static struct platform_device *smdk2440_devices[] __initdata = {
        &s3c_device_ohci,
        &s3c_device_lcd,
        &s3c_device_wdt,
        &s3c_device_i2c0,
        &s3c_device_iis,
        &smdk2440_device_eth,
};

我们需要在smdk2440_devices数组添加&s3c_device_rtc(platform设备s3c_device_rtc是通过plat/devs.h头文件引入的)。

4.2 s3c_rtc_probe

下面来看s3c_rtc_driver的probe函数s3c_rtc_probe:

static int s3c_rtc_probe(struct platform_device *pdev)
{
        struct s3c_rtc *info = NULL;
        struct rtc_time rtc_tm;
        struct resource *res;
        int ret;

        info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);  // 动态分配s3c_rtc结构
        if (!info)
                return -ENOMEM;

        /* find the IRQs */
        info->irq_tick = platform_get_irq(pdev, 1);        // 获取IRQ_TICK时钟节拍中断
        if (info->irq_tick < 0) {
                dev_err(&pdev->dev, "no irq for rtc tick\n");
                return info->irq_tick;
        }

        info->dev = &pdev->dev;
        info->data = of_device_get_match_data(&pdev->dev);  // 通过过设备节点,获取与之匹配的设备驱动of_match_table里面的data属性,由于我们没有使用设备树,因此这里需要修改为&s3c2410_rtc_data
        if (!info->data) {
                dev_err(&pdev->dev, "failed getting s3c_rtc_data\n");
                return -EINVAL;
        }
        spin_lock_init(&info->pie_lock);
        spin_lock_init(&info->alarm_lock);

        platform_set_drvdata(pdev, info);             //  设置驱动数据 pdev->dev.plat_data = info

        info->irq_alarm = platform_get_irq(pdev, 0);  // 获取IRQ_RTC闹钟中断
        if (info->irq_alarm < 0) {
                dev_err(&pdev->dev, "no irq for alarm\n");
                return info->irq_alarm;
        }

        dev_dbg(&pdev->dev, "s3c2410_rtc: tick irq %d, alarm irq %d\n",
                info->irq_tick, info->irq_alarm);

        /* get the memory region */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  // 获取IO内存资源
        info->base = devm_ioremap_resource(&pdev->dev, res);   // 物理地址到虚拟地址映射
        if (IS_ERR(info->base))
                return PTR_ERR(info->base);

        info->rtc_clk = devm_clk_get(&pdev->dev, "rtc");      // 获取rtc时钟
        if (IS_ERR(info->rtc_clk)) {
                ret = PTR_ERR(info->rtc_clk);
                if (ret != -EPROBE_DEFER)
                        dev_err(&pdev->dev, "failed to find rtc clock\n");
                else
                        dev_dbg(&pdev->dev, "probe deferred due to missing rtc clk\n");
                return ret;
        }
        ret = clk_prepare_enable(info->rtc_clk);               // 时钟准备和使能
        if (ret)
                return ret;
        if (info->data->needs_src_clk) {   // 未设置,跳过
                info->rtc_src_clk = devm_clk_get(&pdev->dev, "rtc_src");
                if (IS_ERR(info->rtc_src_clk)) {
                        ret = PTR_ERR(info->rtc_src_clk);
                        if (ret != -EPROBE_DEFER)
                                dev_err(&pdev->dev,
                                        "failed to find rtc source clock\n");
                        else
                                dev_dbg(&pdev->dev,
                                        "probe deferred due to missing rtc src clk\n");
                        goto err_src_clk;
                }
                ret = clk_prepare_enable(info->rtc_src_clk);
                if (ret)
                        goto err_src_clk;
        }

        /* check to see if everything is setup correctly */
        if (info->data->enable)                        // 设置RTCCON寄存器第0位值,RTC使能     
                info->data->enable(info);

        dev_dbg(&pdev->dev, "s3c2410_rtc: RTCCON=%02x\n",
                readw(info->base + S3C2410_RTCCON));   // 获取RTC控制寄存器的值

        device_init_wakeup(&pdev->dev, 1);             // 让电源管理支持唤醒功能

        /* Check RTC Time */
        if (s3c_rtc_gettime(&pdev->dev, &rtc_tm)) {    // 读取BCD寄存器获取当前时间
                rtc_tm.tm_year  = 100;
                rtc_tm.tm_mon   = 0;
                rtc_tm.tm_mday  = 1;
                rtc_tm.tm_hour  = 0;
                rtc_tm.tm_min   = 0;
                rtc_tm.tm_sec   = 0;

                s3c_rtc_settime(&pdev->dev, &rtc_tm);    // 如果获取失败,则设置BCD寄存器的值为rtc_tm中表示的事件

                dev_warn(&pdev->dev, "warning: invalid RTC value so initializing it\n");
        }

        /* register RTC and exit */
        info->rtc = devm_rtc_device_register(&pdev->dev, "s3c", &s3c_rtcops,   // 注册RTC设备
                                             THIS_MODULE);
        if (IS_ERR(info->rtc)) {
                dev_err(&pdev->dev, "cannot attach rtc\n");
                ret = PTR_ERR(info->rtc);
                goto err_nortc;
        }

        ret = devm_request_irq(&pdev->dev, info->irq_alarm, s3c_rtc_alarmirq,   // 申请IRQ_RTC中断,中断处理函数为s3c_rtc_alarmirq
                               0, "s3c2410-rtc alarm", info);
        if (ret) {
                dev_err(&pdev->dev, "IRQ%d error %d\n", info->irq_alarm, ret);
                goto err_nortc;
        }

        ret = devm_request_irq(&pdev->dev, info->irq_tick, s3c_rtc_tickirq,     // 申请IRQ_TICK中断,中断处理函数为s3c_rtc_tickirq
                               0, "s3c2410-rtc tick", info);
        if (ret) {
                dev_err(&pdev->dev, "IRQ%d error %d\n", info->irq_tick, ret);
                goto err_nortc;
        }

        if (info->data->select_tick_clk)   // 未设置,跳过
                info->data->select_tick_clk(info);
        info->rtc->max_user_freq = 128;      // 注册rtc设备时默认设置的是64,这里需要修改为128
        s3c_rtc_setfreq(info, 1);    // 设置TICON寄存器,设置时钟节拍计数值

        s3c_rtc_disable_clk(info);   // 禁止时钟 

        return 0;

err_nortc:
        if (info->data->disable)        // 设置RTCCON寄存器第0位值,RTC禁止   
                info->data->disable(info);

        if (info->data->needs_src_clk)  // 未设置,跳过
                clk_disable_unprepare(info->rtc_src_clk);
err_src_clk:
        clk_disable_unprepare(info->rtc_clk);

        return ret;
}

s3c_rtc_probe函数实现的基本上是硬件相关的操作:

  • 首先获取与之匹配的platform设备中描述的硬件相关的资源,如:
    • 获取RTC外设寄存器地址,完成寄存器物理地址到虚拟地址映射;
    • 获取IRQ_RTC闹钟中断,并注册IRQ_RTC中断,中断处理函数为s3c_rtc_alarmirq;
    • 获取IRQ_TICK时钟节拍中断,并注册IRQ_TICK中断,中断处理函数为s3c_rtc_tickirq;
  • 调用clk_prepare_enable使能RTC外设时钟,本质上就是配置CLKCON时钟控制寄存器的RTC位(位14控制进入RTC模块的PCLK);
  • 调用info->data->enable设置RTCCON控制寄存器第0位的值(使能RTC);info->data->enable被设置成了s3c24xx_rtc_enable;需要注意的是:
    • 这里并没有使能INT_TICK时钟节拍中断(这个是通过ioctl命令RTC_PIE_ON开启的);
    • 这里并没有使能INT_RTC闹钟中断(这个是通过ioctl命令RTC_ALE_ON开启的);
  • 调用device_init_wakeup;
  • 调用s3c_rtc_gettime获取rtc时间;
  • 调用devm_rtc_device_register函数向内核注册一个RTC设备;
    • 首先动态分配一个rtc设备,类型为rtc_device:
    • 置rtc->dev成员:
      • class设置为rtc_class;
      • groups设置为rtc_get_dev_attribute_groups;
      • release设置为rtc_device_release;
      • init_name设置为rtc%d;
      • parent设置为platform设备的dev;
    • 设置rtc成员,比如set_offset_nsec、irq_freq、max_user_freq、irq_lock、irq_queue、id等;
    • 设置rtc->ops为&s3c_rtcops;
    • 调用rtc_dev_prepare初始化字符设备rtc->char_dev设置主设备号为MAJOR(rtc_devt),次设备号为rtc->id,设置字符设备的文件操作集为rtc_dev_fops;
    • 调用cdev_device_add(&rtc->char_dev, &rtc->dev)注册字符设备;
      • 其内部先是调用了cdev_add(&rtc->char_dev, rtc->dev.devt, 1)将1个字符设备添加到内核;
      • 然后调用device_add(&rtc->dev)注册device设备,这样在模块加载的时候,udev daemon就会自动为我们创建设备节点文件/dev/rtc%d;
  • 调用s3c_rtc_setfreq设置时钟节拍寄存器第[6:0]位的值,即设置TICK时钟计数的值,使其时钟节拍频率为1
  • 调用s3c_rtc_disable_clk函数,其内部调用clk_disable禁止时钟;
  • 调用info->data->disable设置RTCCON控制寄存器第0位的值(禁止RTC),同时禁止INT_TICK中断;info->data->disable被设置成了s3c24xx_rtc_disable;

由于我们没有采用设备树的匹配方式,我们需要修改上面的代码:

info->data = &s3c2410_rtc_data;
4.2.1 s3c2410_rtc_data

s3c2410_rtc_data定义在drivers/rtc/rtc-s3c.c文件中,这里面的函数后面会介绍;

static struct s3c_rtc_data const s3c2410_rtc_data = {
        .max_user_freq          = 128,
        .irq_handler            = s3c24xx_rtc_irq,           // INT_RTC、INT_TICK中断处理函数 
        .set_freq               = s3c2410_rtc_setfreq,       // 设置时钟节拍中断频率
        .enable_tick            = s3c24xx_rtc_enable_tick,   // 读取TICNT寄存器位7(INT_TICK中断使能位)
        .save_tick_cnt          = s3c24xx_rtc_save_tick_cnt,
        .restore_tick_cnt       = s3c24xx_rtc_restore_tick_cnt,
        .enable                 = s3c24xx_rtc_enable,     // RTC使能,本质上就是配置RTCCON寄存器位0为1
        .disable                = s3c24xx_rtc_disable,    // RTC禁止,本质上就是配置RTCCON寄存器位0为0
};
4.2.2 s3c24xx_rtc_enable

s3c24xx_rtc_enable定义在drivers/rtc/rtc-s3c.c文件中,该函数的主要作用就是通过设置RTCCON控制寄存器来使能RTC:

static void s3c24xx_rtc_enable(struct s3c_rtc *info)
{
        unsigned int con, tmp;

        con = readw(info->base + S3C2410_RTCCON);     // 获取RTCCON控制寄存器的值
        /* re-enable the device, and check it is ok */
        if ((con & S3C2410_RTCCON_RTCEN) == 0) {                 // 如果RTC控制使能位为禁止,设置为使能
                dev_info(info->dev, "rtc disabled, re-enabling\n");

                tmp = readw(info->base + S3C2410_RTCCON);        // 读取RTCCON控制寄存器值
                writew(tmp | S3C2410_RTCCON_RTCEN, info->base + S3C2410_RTCCON);   // RTCEN位设置为1,即RTC控制使能位设置为使能
        }

        if (con & S3C2410_RTCCON_CNTSEL) {           // BCD计数选择位如果为1的话,设置为0(融入BCD计数器)
                dev_info(info->dev, "removing RTCCON_CNTSEL\n");

                tmp = readw(info->base + S3C2410_RTCCON);
                writew(tmp & ~S3C2410_RTCCON_CNTSEL,
                       info->base + S3C2410_RTCCON);
        }

        if (con & S3C2410_RTCCON_CLKRST) {          // 如果RTC时钟计数复位位为1,设置为0(不复位)
                dev_info(info->dev, "removing RTCCON_CLKRST\n");

                tmp = readw(info->base + S3C2410_RTCCON);
                writew(tmp & ~S3C2410_RTCCON_CLKRST,
                       info->base + S3C2410_RTCCON);
        }
}
4.2.3 device_init_wakeup

device_init_wakeup函数定义在drivers/base/power/wakeup.c:

/**
 * device_init_wakeup - Device wakeup initialization.
 * @dev: Device to handle.
 * @enable: Whether or not to enable @dev as a wakeup device.
 *
 * By default, most devices should leave wakeup disabled.  The exceptions are
 * devices that everyone expects to be wakeup sources: keyboards, power buttons,
 * possibly network interfaces, etc.  Also, devices that don't generate their
 * own wakeup requests but merely forward requests from one bus to another
 * (like PCI bridges) should have wakeup enabled by default.
 */
int device_init_wakeup(struct device *dev, bool enable)
{
        int ret = 0;

        if (!dev)
                return -EINVAL;

        if (enable) {
                device_set_wakeup_capable(dev, true);
                ret = device_wakeup_enable(dev);
        } else {
                device_wakeup_disable(dev);
                device_set_wakeup_capable(dev, false);
        }

        return ret;
}
4.2.4 s3c_rtc_gettime

 s3c_rtc_gettime函数定义在drivers/rtc/rtc-s3c.c,该函数的作用就是通过读取BCD寄存器获取当前时间;

/* Time read/write */
static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
        struct s3c_rtc *info = dev_get_drvdata(dev);   // 获取dev->plat_data
        unsigned int have_retried = 0;
        int ret;

        ret = s3c_rtc_enable_clk(info);    // clk_enable时钟使能  成功返回0
        if (ret)    
                return ret;

retry_get_time:
        rtc_tm->tm_min  = readb(info->base + S3C2410_RTCMIN);     // 获取BCD分寄存器值
        rtc_tm->tm_hour = readb(info->base + S3C2410_RTCHOUR);    // 获取BCD时寄存器值
        rtc_tm->tm_mday = readb(info->base + S3C2410_RTCDATE);    // 获取BCD日寄存器值
        rtc_tm->tm_mon  = readb(info->base + S3C2410_RTCMON);     // 获取BCD月寄存器值  
        rtc_tm->tm_year = readb(info->base + S3C2410_RTCYEAR);    // 获取BCD年寄存器值
        rtc_tm->tm_sec  = readb(info->base + S3C2410_RTCSEC);     // 获取BCD秒寄存器值   

        /* the only way to work out whether the system was mid-update
         * when we read it is to check the second counter, and if it
         * is zero, then we re-try the entire read
         */

        if (rtc_tm->tm_sec == 0 && !have_retried) {   // 判断秒寄存器中是0,则表示过去了一分钟,那么小时,天,月,等寄存器中的值都可能已经变化,需要重新读取这些寄存器的值
                have_retried = 1;
                goto retry_get_time;
        }

        rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec);    // BCD转10进制 将获取的寄存器值,转换为真正的时间数据
        rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min);
        rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour);
        rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday);
        rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon);
        rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);

        s3c_rtc_disable_clk(info);    // 调用clk_disable禁止时钟

        rtc_tm->tm_year += 100;       // 由于tm_year以1900位基准,而S3C2410_RTCYEAR能表示的范围为0~99,因此这里+100就能表示20000~2099年
        rtc_tm->tm_mon -= 1;

        dev_dbg(dev, "read time %ptR\n", rtc_tm);
        return 0;
}
 4.2.5 s3c_rtc_setfreq

s3c_rtc_setfreq函数调用info->data->set_freq函数,根据时钟节拍频率设置TICK时钟计数的值:

/* Set RTC frequency */
static int s3c_rtc_setfreq(struct s3c_rtc *info, int freq)
{
        int ret;

        if (!is_power_of_2(freq))
                return -EINVAL;

        ret = s3c_rtc_enable_clk(info);     // 使能时钟
        if (ret)
                return ret;
        spin_lock_irq(&info->pie_lock);

        if (info->data->set_freq)
                info->data->set_freq(info, freq);

        spin_unlock_irq(&info->pie_lock);  
        s3c_rtc_disable_clk(info);          // 禁止时钟

        return 0;
}

由于info->data->set_freq被设置为了s3c2410_rtc_setfreq,所以这里最终调用的是s3c2410_rtc_setfreq:

static void s3c2410_rtc_setfreq(struct s3c_rtc *info, int freq)
{
        unsigned int tmp = 0;
        int val;

        tmp = readb(info->base + S3C2410_TICNT);
        tmp &= S3C2410_TICNT_ENABLE;

        val = (info->rtc->max_user_freq / freq) - 1;  // TICK时钟计数值=128/f-1
        tmp |= val;

        writel(tmp, info->base + S3C2410_TICNT);
}
4.2.6 s3c_rtc_disable_clk

 s3c_rtc_disable_clk函数定义在drivers/rtc/rtc-s3c.c,该函数的作用就是通过调用clk_disable来禁止时钟;

static void s3c_rtc_disable_clk(struct s3c_rtc *info)
{
        if (info->data->needs_src_clk)  // 未设置,跳过
                clk_disable(info->rtc_src_clk);     
        clk_disable(info->rtc_clk);
}
4.2.7 s3c24xx_rtc_disable

s3c24xx_rtc_disable函数定义在drivers/rtc/rtc-s3c.c,该函数的作用就是通过设RTCCON控制寄存器禁止RTC,同时禁止INT_TICK中断;

static void s3c24xx_rtc_disable(struct s3c_rtc *info)
{
        unsigned int con;

        con = readw(info->base + S3C2410_RTCCON);   // 读取RTCCON控制寄存器的值
        con &= ~S3C2410_RTCCON_RTCEN;               // 禁止RTC
        writew(con, info->base + S3C2410_RTCCON);   // 写回 

        con = readb(info->base + S3C2410_TICNT);    // 读取TICNT时钟节拍计数寄存器
        con &= ~S3C2410_TICNT_ENABLE;               // 禁止INT_TICK中断使能
        writeb(con, info->base + S3C2410_TICNT);    // 写回
}

4.3 中断处理函数

INT_RTC闹钟中断对应的中断处理函数为s3c_rtc_alarmirq,INT_TICK时钟节拍中断对应的中断处理函数为s3c_rtc_tickirq,这两个中断处理函数实际上调用的都是rtc_update_irq函数,该函数主要作用就是更新中断数据rtc->irqdata的值。

4.3.1 s3c_rtc_alarmirq

当闹钟中断触发时,会执行s3c_rtc_alarmirq函数,该函数位于drivers/rtc/rtc-s3c.c文件:

static irqreturn_t s3c_rtc_alarmirq(int irq, void *id)
{
        struct s3c_rtc *info = (struct s3c_rtc *)id;

        if (info->data->irq_handler)
                info->data->irq_handler(info, S3C2410_INTP_ALM);

        return IRQ_HANDLED;
}

函数内部调用了info->data->irq_handler也就是s3c24xx_rtc_irq函数:

static void s3c24xx_rtc_irq(struct s3c_rtc *info, int mask)
{
        rtc_update_irq(info->rtc, 1, RTC_AF | RTC_IRQF);  // 更新中断数据rtc->irqdata的值
}
4.3.2 rtc_update_irq

rtc_update_irq函数位于drivers/rtc/interface.c文件:

/**
 * rtc_update_irq - Triggered when a RTC interrupt occurs.
 * @rtc: the rtc device
 * @num: how many irqs are being reported (usually one)
 * @events: mask of RTC_IRQF with one or more of RTC_PF, RTC_AF, RTC_UF
 * Context: any
 */
void rtc_update_irq(struct rtc_device *rtc,
                    unsigned long num, unsigned long events)
{
        if (IS_ERR_OR_NULL(rtc))
                return;

        pm_stay_awake(rtc->dev.parent);
        schedule_work(&rtc->irqwork);
}

执行rtc->irqwork里的任务,也就是函数rtc_timer_do_work。

4.3.3 rtc_timer_do_work

rtc_timer_do_work函数位于drivers/rtc/interface.c:

/**
 * rtc_timer_do_work - Expires rtc timers
 * @rtc rtc device
 * @timer timer being removed.
 *
 * Expires rtc timers. Reprograms next alarm event if needed.
 * Called via worktask.
 *
 * Serializes access to timerqueue via ops_lock mutex
 */
void rtc_timer_do_work(struct work_struct *work)
{
        struct rtc_timer *timer;
        struct timerqueue_node *next;
        ktime_t now;
        struct rtc_time tm;

        struct rtc_device *rtc =
                container_of(work, struct rtc_device, irqwork);

        mutex_lock(&rtc->ops_lock);
again:
        __rtc_read_time(rtc, &tm);                                  // 获取当前时间
        now = rtc_tm_to_ktime(tm);
        while ((next = timerqueue_getnext(&rtc->timerqueue))) {     // 利用container(next,....)这里可以从定时器队列中获取到下一个要触发的定时器(通过最小堆定时器算法实现的)
                if (next->expires > now)           // 闹钟时间未到
                        break;

                /* expire timer */
                timer = container_of(next, struct rtc_timer, node);  
                timerqueue_del(&rtc->timerqueue, &timer->node);   // 从定时器队列中移除闹钟定时器
                trace_rtc_timer_dequeue(timer);
                timer->enabled = 0;
                if (timer->func)
                        timer->func(timer->rtc);    // 执行定时器超时处理函数,也就是rtc_aie_update_irq

                trace_rtc_timer_fired(timer);
                /* Re-add/fwd periodic timers */
                if (ktime_to_ns(timer->period)) {   // 不会进入
                        timer->node.expires = ktime_add(timer->node.expires,
                                                        timer->period);
                        timer->enabled = 1;
                        timerqueue_add(&rtc->timerqueue, &timer->node);
                        trace_rtc_timer_enqueue(timer);
                }
        }

        /* Set next alarm */
        if (next) {   // 定时器队列rtc->timerqueue中还设有未触发的闹钟
                struct rtc_wkalrm alarm;
                int err;
                int retry = 3;

                alarm.time = rtc_ktime_to_tm(next->expires);
                alarm.enabled = 1;
reprogram:
                err = __rtc_set_alarm(rtc, &alarm);   // 设置闹钟时间为下一个会触发的闹钟时间,这样就可以触发下一个闹钟中断了
                if (err == -ETIME) {
                        goto again;
                } else if (err) {
                        if (retry-- > 0)
                                goto reprogram;

                        timer = container_of(next, struct rtc_timer, node);
                        timerqueue_del(&rtc->timerqueue, &timer->node);
                        trace_rtc_timer_dequeue(timer);
                        timer->enabled = 0;
                        dev_err(&rtc->dev, "__rtc_set_alarm: err=%d\n", err);
                        goto again;
                }
        } else {
                rtc_alarm_disable(rtc);  // 调用rtc->ops->alarm_irq_enable(rtc->dev.parent, false)关闭INT_RTC闹钟中断
        }

        pm_relax(rtc->dev.parent);
        mutex_unlock(&rtc->ops_lock);
}

如果我们开启了rtc->aie_timer闹钟定时器,当闹钟时间到时,就会触发闹钟中断,并执行闹钟定时器超时处理函数rtc_aie_update_irq。

4.3.4  rtc_aie_update_irq

aie_timer闹钟定时器超时函数rtc_aie_update_irq主要作用就是设置中断数据irqdata,同时调用wake_up_interruptible(&rtc->irq_queue)唤醒堵塞进程;

/**
 * rtc_handle_legacy_irq - AIE, UIE and PIE event hook
 * @rtc: pointer to the rtc device
 *
 * This function is called when an AIE, UIE or PIE mode interrupt
 * has occurred (or been emulated).
 *
 */
void rtc_handle_legacy_irq(struct rtc_device *rtc, int num, int mode)
{
        unsigned long flags;

        /* mark one irq of the appropriate mode */
        spin_lock_irqsave(&rtc->irq_lock, flags);
        rtc->irq_data = (rtc->irq_data + (num << 8)) | (RTC_IRQF | mode);
        spin_unlock_irqrestore(&rtc->irq_lock, flags);

        wake_up_interruptible(&rtc->irq_queue);
        kill_fasync(&rtc->async_queue, SIGIO, POLL_IN);
}

/**
 * rtc_aie_update_irq - AIE mode rtctimer hook
 * @rtc: pointer to the rtc_device
 *
 * This functions is called when the aie_timer expires.
 */
void rtc_aie_update_irq(struct rtc_device *rtc)
{
        rtc_handle_legacy_irq(rtc, 1, RTC_AF);
}
4.3.5 s3c_rtc_tickirq

当时钟节拍中断触发时,会执行s3c_rtc_tickirq函数,该函数位于drivers/rtc/rtc-s3c.c文件:

/* IRQ Handlers */
static irqreturn_t s3c_rtc_tickirq(int irq, void *id)
{
        struct s3c_rtc *info = (struct s3c_rtc *)id;

        if (info->data->irq_handler)
                info->data->irq_handler(info, S3C2410_INTP_TIC);

        return IRQ_HANDLED;
}

同样内部调用的info->data->irq_handler。

4.4 s3c_rtc_remove

static int s3c_rtc_remove(struct platform_device *pdev)
{
        struct s3c_rtc *info = platform_get_drvdata(pdev);

        s3c_rtc_setaie(info->dev, 0);             // 关闭INT_RTC闹钟中断

        if (info->data->needs_src_clk)            // 未指定,跳过  
                clk_unprepare(info->rtc_src_clk);
        clk_unprepare(info->rtc_clk);

        return 0;
}

4.5 流程图

下面绘制了RTC驱动注册流程:

 五、RTC字符设备操作集rtc_dev_fops

在RTC设备驱动注册过程中,RTC字符设备文件操作集被设置为了rtc_dev_fops,定义在drivers/rtc/dev.c:

static const struct file_operations rtc_dev_fops = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
        .read           = rtc_dev_read,
        .poll           = rtc_dev_poll,
        .unlocked_ioctl = rtc_dev_ioctl,
        .open           = rtc_dev_open,
        .release        = rtc_dev_release,
        .fasync         = rtc_dev_fasync,
};

所以当我们对RTC设备节点/dev/rtc%d进行打开、控制等操作时,会执行rtc_dev_fops中对应的函数。

5.1 打开RTC设备(rtc_dev_open)

当我们应用层open(”/dev/rtc%d”)时,就会调用 rtc_dev_open函数:

static int rtc_dev_open(struct inode *inode, struct file *file)
{
        struct rtc_device *rtc = container_of(inode->i_cdev,     // 获取对应的rtc_device
                                        struct rtc_device, char_dev);

        if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))    // 如果RTC设备处于空闲状态,则设置为繁忙标志位;否则直接返回BUST
                return -EBUSY;

        file->private_data = rtc;  // 设置file结构体的私有成员等于rtc_device,再次执行ioctl等函数时,直接就可以提取file->private_data即可

        spin_lock_irq(&rtc->irq_lock);    // 获取自旋锁 + 关中断
        rtc->irq_data = 0;
        spin_unlock_irq(&rtc->irq_lock);  // 释放自旋锁 + 开中断

        return 0;
}

5.2 读取RTC设备(rtc_dev_read)

当我们应用层open后,使用 read(...)时,就会调用rtc_dev_read函数,当时钟中断触发时会将中断数据rtc->irqdata的值拷贝到用户空间;通过irqdata我们可以知道发生了哪一种中断(因为INT_RTC、INT_TICK中断设置的值不一样);

static ssize_t
rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
        struct rtc_device *rtc = file->private_data;

        DECLARE_WAITQUEUE(wait, current);  // 定义并初始化一个名为wait的等待队列元素,并设置private为当前进程
        unsigned long data;
        ssize_t ret;

        if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
                return -EINVAL;

        add_wait_queue(&rtc->irq_queue, &wait);            // 向等待队列irq_queue添加元素wait
        do {
                __set_current_state(TASK_INTERRUPTIBLE);   // 将当前进程的状态设置成TASK_INTERRUPTIBLE

                spin_lock_irq(&rtc->irq_lock);
                data = rtc->irq_data;                     // 获取irq中断数据,在RTC中断处理函数中修改该值
                rtc->irq_data = 0;
                spin_unlock_irq(&rtc->irq_lock);

                if (data != 0) {
                        ret = 0;
                        break;
                }
                if (file->f_flags & O_NONBLOCK) {   // 非堵塞
                        ret = -EAGAIN;
                        break;
                }
                if (signal_pending(current)) {     // 如果有信号处理,跳出处理信号,然后便会进入对应的signal处理函数
                        ret = -ERESTARTSYS;
                        break;
                }
                schedule();   // 让出CPU,进程进入睡眠,那什么时候会唤醒该进程呢,一般当会在RTC中断中调用wake_up_interruptible(&rtc->irq_queue)唤醒该进程
        } while (1);
        set_current_state(TASK_RUNNING);             // 将当前进程的状态设置成TASK_INTERRUPTIBLE
        remove_wait_queue(&rtc->irq_queue, &wait);   // 从等待队列移除元素wait

        if (ret == 0) {
                if (sizeof(int) != sizeof(long) &&
                    count == sizeof(unsigned int))
                        ret = put_user(data, (unsigned int __user *)buf) ?:
                                sizeof(unsigned int);
                else
                        ret = put_user(data, (unsigned long __user *)buf) ?:
                                sizeof(unsigned long);
        }
        return ret;
}

如果是堵塞模式下,读取RTC设备,将会将当前进程设置为TASK_INTERRUPTIBLE,并调用schedule让出CPU。

然后在INT_RTC、INT_TICK中断处理函数中调用wake_up_interruptible(&rtc->irq_queue)唤醒当前进程。

5.3 控制RTC设备(rtc_dev_ioctl)

当我们应用层open后,使用 ioctl(int fd, int cmd, ...)时,就会调用rtc_dev_ioctl函数,该函数实现了许多命令,比如读取/设置时间、读取/设置闹钟时间,开启/关闭闹钟定时器,开启/关闭时钟节拍中断:

static long rtc_dev_ioctl(struct file *file,
                          unsigned int cmd, unsigned long arg)
{
        int err = 0;
        struct rtc_device *rtc = file->private_data;    // 获取rtc_device
        const struct rtc_class_ops *ops = rtc->ops;     // 获取rtc设备操作集
        struct rtc_time tm;
        struct rtc_wkalrm alarm;
        void __user *uarg = (void __user *)arg;

        err = mutex_lock_interruptible(&rtc->ops_lock);    // 获取互斥锁,获取失败,进入睡眠
        if (err)
                return err;

        /* check that the calling task has appropriate permissions
         * for certain ioctls. doing this check here is useful
         * to avoid duplicate code in each driver.
         */
        switch (cmd) {               
        case RTC_EPOCH_SET:
        case RTC_SET_TIME:                if (!capable(CAP_SYS_TIME))
                        err = -EACCES;
                break;

        case RTC_IRQP_SET:                if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))
                        err = -EACCES;
                break;

        case RTC_PIE_ON:
                if (rtc->irq_freq > rtc->max_user_freq &&
                    !capable(CAP_SYS_RESOURCE))
                        err = -EACCES;
                break;
        }

        if (err)
                goto done;

        /*
         * Drivers *SHOULD NOT* provide ioctl implementations
         * for these requests.  Instead, provide methods to
         * support the following code, so that the RTC's main
         * features are accessible without using ioctls.
         *
         * RTC and alarm times will be in UTC, by preference,
         * but dual-booting with MS-Windows implies RTCs must
         * use the local wall clock time.
         */

        switch (cmd) {
        case RTC_ALM_READ:             // 读取闹钟时间
                mutex_unlock(&rtc->ops_lock);

                err = rtc_read_alarm(rtc, &alarm);
                if (err < 0)
                        return err;

                if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
                        err = -EFAULT;
                return err;
        case RTC_ALM_SET:            // 设置闹钟时间,闹钟时间格式只包含 时:分:秒,不包含年月日;需要注意的是设置闹钟时间,并不会配置闹钟控制寄存器RTCALM第6位为1(即全局闹钟使能),也就是说这里并不会打开INT_RTC闹钟中断
                mutex_unlock(&rtc->ops_lock);

                if (copy_from_user(&alarm.time, uarg, sizeof(tm)))  // 拷贝用户空间数据到内核空间,即获取需要设置的闹钟时间
                        return -EFAULT;

                alarm.enabled = 0;   // 设置为0,表示不会开启闹钟
                alarm.pending = 0;
                alarm.time.tm_wday = -1;
                alarm.time.tm_yday = -1;
                alarm.time.tm_isdst = -1;

                /* RTC_ALM_SET alarms may be up to 24 hours in the future.
                 * Rather than expecting every RTC to implement "don't care"
                 * for day/month/year fields, just force the alarm to have
                 * the right values for those fields.
                 *
                 * RTC_WKALM_SET should be used instead.  Not only does it
                 * eliminate the need for a separate RTC_AIE_ON call, it
                 * doesn't have the "alarm 23:59:59 in the future" race.
                 *
                 * NOTE:  some legacy code may have used invalid fields as
                 * wildcards, exposing hardware "periodic alarm" capabilities.
                 * Not supported here.
                 */
                {
                        time64_t now, then;

                        err = rtc_read_time(rtc, &tm);   // 获取当前时间
                        if (err < 0)
                                return err;
                        now = rtc_tm_to_time64(&tm);

                        alarm.time.tm_mday = tm.tm_mday;   // 设置天为今天
                        alarm.time.tm_mon = tm.tm_mon;     // 设置月为当前月 
                        alarm.time.tm_year = tm.tm_year;   // 设置年为当前年
                        err  = rtc_valid_tm(&alarm.time);
                        if (err < 0)
                                return err;
                        then = rtc_tm_to_time64(&alarm.time);

                        /* alarm may need to wrap into tomorrow */
                        if (then < now) {  // 闹钟时间已过,闹钟时间设置为明天
                                rtc_time64_to_tm(now + 24 * 60 * 60, &tm);
                                alarm.time.tm_mday = tm.tm_mday;
                                alarm.time.tm_mon = tm.tm_mon;
                                alarm.time.tm_year = tm.tm_year;
                        }
                }

                return rtc_set_alarm(rtc, &alarm);  // 设置闹钟时间

        case RTC_RD_TIME:            // 读取时间
                mutex_unlock(&rtc->ops_lock);

                err = rtc_read_time(rtc, &tm);
                if (err < 0)
                        return err;

                if (copy_to_user(uarg, &tm, sizeof(tm)))
                        err = -EFAULT;
                return err;

        case RTC_SET_TIME:           // 设置时间
                mutex_unlock(&rtc->ops_lock);

                if (copy_from_user(&tm, uarg, sizeof(tm)))
                        return -EFAULT;

                return rtc_set_time(rtc, &tm);

        case RTC_PIE_ON:    // 开启INT_TICK时钟节拍中断,设置时钟节拍计数寄存器第7位的值为1(使能INT_TICK中断)
                err = rtc_irq_set_state(rtc, 1);
                break;

        case RTC_PIE_OFF:   // 关闭INT_TICK时钟节拍中断,设置时钟节拍计数寄存器第7位的值为0(禁止INT_TICK中断)
                err = rtc_irq_set_state(rtc, 0);
                break;

        case RTC_AIE_ON:     // 开启INT_RTC闹钟中断;设置闹钟控制器寄存器位第6位的值为1(全局闹钟使能),只有全局闹钟使能才会触发INT_RTC闹钟中断,何时触发中断,是由闹钟时间来决定的,闹钟时间是通过RTC_ALM_SET设置的
                mutex_unlock(&rtc->ops_lock);
                return rtc_alarm_irq_enable(rtc, 1);   // 调用rtc->ops->alarm_irq_enable,即s3c_rtc_setaie

        case RTC_AIE_OFF:   // 关闭INT_RTC闹钟中断,设置闹钟控制器寄存器第6位的值为0(全局闹钟禁)止,此时关闭了INT_RTC闹钟中断
                mutex_unlock(&rtc->ops_lock);
                return rtc_alarm_irq_enable(rtc, 0);

        case RTC_UIE_ON:
                mutex_unlock(&rtc->ops_lock);
                return rtc_update_irq_enable(rtc, 1);

        case RTC_UIE_OFF:
                mutex_unlock(&rtc->ops_lock);
                return rtc_update_irq_enable(rtc, 0);

        case RTC_IRQP_SET:   // 设置时钟节拍中断频率
                err = rtc_irq_set_freq(rtc, arg);
                break;

        case RTC_IRQP_READ:    // 读取时钟节拍中断  频率
                err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
                break;

        case RTC_WKALM_SET:
                mutex_unlock(&rtc->ops_lock);
                if (copy_from_user(&alarm, uarg, sizeof(alarm)))
                        return -EFAULT;

                return rtc_set_alarm(rtc, &alarm);

        case RTC_WKALM_RD:
                mutex_unlock(&rtc->ops_lock);
                err = rtc_read_alarm(rtc, &alarm);
                if (err < 0)
                        return err;

                if (copy_to_user(uarg, &alarm, sizeof(alarm)))
                        err = -EFAULT;
                return err;
        default:
                /* Finally try the driver's ioctl interface */
                if (ops->ioctl) {
                        err = ops->ioctl(rtc->dev.parent, cmd, arg);
                        if (err == -ENOIOCTLCMD)
                                err = -ENOTTY;
                } else {
                        err = -ENOTTY;
                }
                break;
        }

done:
        mutex_unlock(&rtc->ops_lock);
        return err;
}
5.3.1 读取RTC时间

这里我们以读取RTC时间为例,其调用的rtc_read_time(rtc, &tm),最终调用的rtc->ops->read_time,由于rtc->ops被设置为了&s3c_rtcops:

static const struct rtc_class_ops s3c_rtcops = {
        .read_time      = s3c_rtc_gettime,     
        .set_time       = s3c_rtc_settime,
        .read_alarm     = s3c_rtc_getalarm,
        .set_alarm      = s3c_rtc_setalarm,
        .proc           = s3c_rtc_proc,
        .alarm_irq_enable = s3c_rtc_setaie,     // INT_RTC闹钟中断开启、关闭函数
};

所以最终调用了s3c_rtc_gettime函数,这个函数上面已经介绍过了,就不重复介绍了。

5.3.2 设置RTC时间

设置RTC时间,调用的是rtc_set_time(rtc, &tm):

int rtc_set_time(struct rtc_device *rtc, struct rtc_time *tm)
{
        int err;

        err = rtc_valid_tm(tm);
        if (err != 0)
                return err;

        err = rtc_valid_range(rtc, tm);
        if (err)
                return err;

        rtc_subtract_offset(rtc, tm);

        err = mutex_lock_interruptible(&rtc->ops_lock);
        if (err)
                return err;

        if (!rtc->ops)
                err = -ENODEV;
        else if (rtc->ops->set_time)
                err = rtc->ops->set_time(rtc->dev.parent, tm);  // 重点
        else
                err = -EINVAL;

        pm_stay_awake(rtc->dev.parent);
        mutex_unlock(&rtc->ops_lock);
        /* A timer might have just expired */
        schedule_work(&rtc->irqwork);  // 执行工作

        trace_rtc_set_time(rtc_tm_to_time64(tm), err);
        return err;
}

最终调用的rtc->ops->set_time,即s3c_rtc_settime;这里时通过向BCD寄存器写入时间;

static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
{
        struct s3c_rtc *info = dev_get_drvdata(dev);
        int year = tm->tm_year - 100;
        int ret;

        dev_dbg(dev, "set time %ptR\n", tm);

        /* we get around y2k by simply not supporting it */

        if (year < 0 || year >= 100) {
                dev_err(dev, "rtc only supports 100 years\n");
                return -EINVAL;
        }

        ret = s3c_rtc_enable_clk(info);
        if (ret)
                return ret;

        writeb(bin2bcd(tm->tm_sec),  info->base + S3C2410_RTCSEC);  // bin转BCD 写入到对应BCD寄存器
        writeb(bin2bcd(tm->tm_min),  info->base + S3C2410_RTCMIN);
        writeb(bin2bcd(tm->tm_hour), info->base + S3C2410_RTCHOUR);
        writeb(bin2bcd(tm->tm_mday), info->base + S3C2410_RTCDATE);
        writeb(bin2bcd(tm->tm_mon + 1), info->base + S3C2410_RTCMON);
        writeb(bin2bcd(year), info->base + S3C2410_RTCYEAR);

        s3c_rtc_disable_clk(info);

        return 0;
}
5.3.3 读取闹钟时间

读取闹钟时间,调用的是rtc_read_alarm(rtc, &alrm),该函数实际上读取的就是闹钟定时器rtc->aie_timer中设置的闹钟时间;这个闹钟时间是通过RTC_ALM_SET命令设置进去的;

int rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
        int err;

        err = mutex_lock_interruptible(&rtc->ops_lock);
        if (err)
                return err;
        if (!rtc->ops) {
                err = -ENODEV;
        } else if (!rtc->ops->read_alarm) {
                err = -EINVAL;
        } else {
                memset(alarm, 0, sizeof(struct rtc_wkalrm));
                alarm->enabled = rtc->aie_timer.enabled;      // 设置闹钟使能位
                alarm->time = rtc_ktime_to_tm(rtc->aie_timer.node.expires);  // 设置闹钟时间
        }
        mutex_unlock(&rtc->ops_lock);

        trace_rtc_read_alarm(rtc_tm_to_time64(&alarm->time), err);
        return err;
}
5.3.4 设置闹钟时间

设置闹钟时间,调用的是rtc_set_alarm(rtc, &alarm),此时传入的alarm.enabled = 0; 

int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
        int err;

        if (!rtc->ops)
                return -ENODEV;
        else if (!rtc->ops->set_alarm)
                return -EINVAL;

        err = rtc_valid_tm(&alarm->time);
        if (err != 0)
                return err;

        err = rtc_valid_range(rtc, &alarm->time);
        if (err)
                return err;

        err = mutex_lock_interruptible(&rtc->ops_lock);
        if (err)
                return err;
        if (rtc->aie_timer.enabled)                     // 如果开启了闹钟定时器,将会将闹钟定时器rtc->aie_timer从定时器队列rtc->timerqueue中移除
                rtc_timer_remove(rtc, &rtc->aie_timer);

        rtc->aie_timer.node.expires = rtc_tm_to_ktime(alarm->time);  // 更新闹钟定时器过期时间
        rtc->aie_timer.period = 0;
        if (alarm->enabled)                           // 如果闹钟开启
err
= rtc_timer_enqueue(rtc, &rtc->aie_timer); mutex_unlock(&rtc->ops_lock); return err; }

如果闹钟没有开启的话,即alarm->enabled=0,这里实际上仅仅会将闹钟时间设置到闹钟定时器rtc->aie_timer中,并不会做额外的工作。

如果闹钟开启的话,将会调用rtc_timer_enqueue函数,将闹钟定时器rtc->aie_timer添加到定时器队列rtc->timerqueue中,同时设置闹钟定时器使能标志位rtc->aie_timer.enabled=1; 如果队列中有多个定时器,还会将调用__rtc_set_alarm设置闹钟时间为最先触发的闹钟时间;

/**
 * rtc_timer_enqueue - Adds a rtc_timer to the rtc_device timerqueue
 * @rtc rtc device
 * @timer timer being added.
 *
 * Enqueues a timer onto the rtc devices timerqueue and sets
 * the next alarm event appropriately.
 *
 * Sets the enabled bit on the added timer.
 *
 * Must hold ops_lock for proper serialization of timerqueue
 */
static int rtc_timer_enqueue(struct rtc_device *rtc, struct rtc_timer *timer)
{
        struct timerqueue_node *next = timerqueue_getnext(&rtc->timerqueue);   // 最小堆,根节点  最小值
        struct rtc_time tm;
        ktime_t now;

        timer->enabled = 1;
        __rtc_read_time(rtc, &tm);
        now = rtc_tm_to_ktime(tm);

        /* Skip over expired timers */
        while (next) {
                if (next->expires >= now)   // 直至闹钟时间未到,才会跳出
                        break;
                next = timerqueue_iterate_next(next);
        }

        timerqueue_add(&rtc->timerqueue, &timer->node);   // 新增aie_timer定时器节点
        trace_rtc_timer_enqueue(timer);
        if (!next || ktime_before(timer->node.expires, next->expires)) {     // timer中设定的闹钟时间更小,更先触发
                struct rtc_wkalrm alarm;
                int err;

                alarm.time = rtc_ktime_to_tm(timer->node.expires);
                alarm.enabled = 1;                                    // 设置标志位
                err = __rtc_set_alarm(rtc, &alarm);                   // 更新闹钟时间
                if (err == -ETIME) {
                        pm_stay_awake(rtc->dev.parent);
                        schedule_work(&rtc->irqwork);
                } else if (err) {
                        timerqueue_del(&rtc->timerqueue, &timer->node);
                        trace_rtc_timer_dequeue(timer);
                        timer->enabled = 0;
                        return err;
                }
        }
        return 0;
}

__rtc_set_alarm最终调用的rtc->ops->set_alarm,即s3c_rtc_setalarm;这里通过设置闹钟寄存器设置闹钟时间,需要注意的是只包含时分秒、月日,不包含年;

static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
        struct s3c_rtc *info = dev_get_drvdata(dev);
        struct rtc_time *tm = &alrm->time;
        unsigned int alrm_en;
        int ret;

        dev_dbg(dev, "s3c_rtc_setalarm: %d, %ptR\n", alrm->enabled, tm);

        ret = s3c_rtc_enable_clk(info);
        if (ret)
                return ret;

        alrm_en = readb(info->base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;     // 读取闹钟控制寄存器全局闹钟使能位的值 
        writeb(0x00, info->base + S3C2410_RTCALM);                               // 设置闹钟控制寄存器的值为0,关闭所有闹钟使能    

        if (tm->tm_sec < 60 && tm->tm_sec >= 0) {       // 如果秒时间在合理范围内,则秒闹钟位使能,将tm封装的秒位由bin格式转换为BCD,写入闹钟秒数据寄存器
                alrm_en |= S3C2410_RTCALM_SECEN;
                writeb(bin2bcd(tm->tm_sec), info->base + S3C2410_ALMSEC);
        }

        if (tm->tm_min < 60 && tm->tm_min >= 0) {      // 设置闹钟分数据寄存器
                alrm_en |= S3C2410_RTCALM_MINEN;
                writeb(bin2bcd(tm->tm_min), info->base + S3C2410_ALMMIN);
        }

        if (tm->tm_hour < 24 && tm->tm_hour >= 0) {   // 设置闹钟时数据寄存器
                alrm_en |= S3C2410_RTCALM_HOUREN;
                writeb(bin2bcd(tm->tm_hour), info->base + S3C2410_ALMHOUR);
        }

        if (tm->tm_mon < 12 && tm->tm_mon >= 0) {     // 设置闹钟月数据寄存器
                alrm_en |= S3C2410_RTCALM_MONEN;
                writeb(bin2bcd(tm->tm_mon + 1), info->base + S3C2410_ALMMON);
        }

        if (tm->tm_mday <= 31 && tm->tm_mday >= 1) {   // 设置闹钟天数据寄存器
                alrm_en |= S3C2410_RTCALM_DAYEN;
                writeb(bin2bcd(tm->tm_mday), info->base + S3C2410_ALMDATE);
        }

        dev_dbg(dev, "setting S3C2410_RTCALM to %08x\n", alrm_en);  

        writeb(alrm_en, info->base + S3C2410_RTCALM);   //  设置闹钟控制寄存器的值

        s3c_rtc_setaie(dev, alrm->enabled);  // 根据alrm->enabled设置闹钟控制寄存器第6位的值 全局闹钟使能/禁止

        s3c_rtc_disable_clk(info);

        return 0;
}
5.3.5 开启闹钟定时器(同开启INT_RTC中断)

开启闹钟定时器aie_timer,对应的命令时RTC_AIE_ON,由于闹钟定时器的超时处理函数rtc_aie_update_irq是在中断处理函数s3c_rtc_alarmirq中触发的,因此该命令实际上执行的操作是开启闹钟中断,同时设置闹钟定时器开启标志位rtc->aie_timer.enabled=1。

调用的是rtc_alarm_irq_enable(rtc,1)函数,如下:

int rtc_alarm_irq_enable(struct rtc_device *rtc, unsigned int enabled)
{
        int err;

        err = mutex_lock_interruptible(&rtc->ops_lock);
        if (err)
                return err;

        if (rtc->aie_timer.enabled != enabled) {  // 闹钟定时器由开启->关闭 或者由关闭->开启
                if (enabled)   // 关闭->开启 需要将闹钟定时器rtc->aie_timer添加到定时器队列rtc->timerqueue中,同时设置闹钟定时器使能标志位rtc->aie_timer.enabled=1,如果队列中有多个定时器,
// 还会将调用__rtc_set_alarm设置闹钟时间为最先触发的闹钟时间
err
= rtc_timer_enqueue(rtc, &rtc->aie_timer); else rtc_timer_remove(rtc, &rtc->aie_timer);   // 开启->关闭 需要将闹钟定时器rtc->aie_timer从定时器队列rtc->timerqueue中移除 } if (err) /* nothing */; else if (!rtc->ops) err = -ENODEV; else if (!rtc->ops->alarm_irq_enable) err = -EINVAL; else err = rtc->ops->alarm_irq_enable(rtc->dev.parent, enabled); // 重点 开INT_RTC中断 mutex_unlock(&rtc->ops_lock); trace_rtc_alarm_irq_enable(enabled, err); return err; }

最终调用的rtc->ops->alarm_irq_enable,即s3c_rtc_setaie;这里通过设置闹钟控制寄存器位第6位的值为1来使能全局闹钟;s3c_rtc_setaie函数定义如下:

/* Update control registers */
static int s3c_rtc_setaie(struct device *dev, unsigned int enabled)
{
        struct s3c_rtc *info = dev_get_drvdata(dev);
        unsigned long flags;
        unsigned int tmp;
        int ret;

        dev_dbg(info->dev, "%s: aie=%d\n", __func__, enabled);

        ret = s3c_rtc_enable_clk(info);
        if (ret)
                return ret;

        tmp = readb(info->base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;  // 读取全局闹钟使能位的值

        if (enabled)  // 使能|禁止
                tmp |= S3C2410_RTCALM_ALMEN;

        writeb(tmp, info->base + S3C2410_RTCALM);        // 写回

        spin_lock_irqsave(&info->alarm_lock, flags);

        if (info->alarm_enabled && !enabled)
                s3c_rtc_disable_clk(info);
        else if (!info->alarm_enabled && enabled)
                ret = s3c_rtc_enable_clk(info);

        info->alarm_enabled = enabled;                  // 闹钟开启|禁止标志位 
        spin_unlock_irqrestore(&info->alarm_lock, flags);

        s3c_rtc_disable_clk(info);

        return ret;
}
5.3.6 开启INT_TICK时钟节拍中断

开启时钟节拍中断,调用的是rtc_irq_set_state(rtc,1);盲猜这里应该是通过设置时钟节拍计数寄存器第7位来使能INT_TICK中断;

/**
 * rtc_irq_set_state - enable/disable 2^N Hz periodic IRQs
 * @rtc: the rtc device
 * @enabled: true to enable periodic IRQs
 * Context: any
 *
 * Note that rtc_irq_set_freq() should previously have been used to
 * specify the desired frequency of periodic IRQ.
 */
int rtc_irq_set_state(struct rtc_device *rtc, int enabled)
{
        int err = 0;

        while (rtc_update_hrtimer(rtc, enabled) < 0)
                cpu_relax();

        rtc->pie_enabled = enabled;

        trace_rtc_irq_set_state(enabled, err);
        return err;
}

六、测试

6.1 源码修改

我们按照第四节的介绍对源码进行部分调整:

  • 修改arch/arm/plat-samsung/devs.c文件s3c_device_rtc变量的name为”s3c-rtc“;
  • 修改arch/arm/mach-s3c24xx/mach-smdk2440.c文件,smdk2440_devices数组添加&s3c_device_rtc;
  • 修改drivers/rtc/rtc-s3c.c文件s3c_rtc_probe函数;
    • 在s3c_rtc_setfreq(info, 1)之前添加info->rtc->max_user_freq = 128;
    • 修改info->data = get_s3c_rtc_data();函数需要在文件开始声明,文件最后定义;
static struct s3c_rtc_data * get_s3c_rtc_data(void)
{
  return &s3c2410_rtc_data;
}

6.2 编译内核

make s3c2440_defconfig
make V=1 uImage

将uImage复制到tftp服务器路径下:

root@zhengyang:/work/sambashare/linux-5.2.8# cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

6.3 烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,然后烧录内核到NAND FLASH:

SMDK2440 # tftp 30000000 uImage
SMDK2440 # nand erase.part kernel
SMDK2440 # nand write 30000000 kernel
SMDK2440 # bootm

6.4 测试

启动后,我们可以看到控制台输出关于RTC相关的信息:

s3c-rtc s3c-rtc: rtc disabled, re-enabling
rtc rtc0: invalid alarm value: 1900-02-01T00:00:00
s3c-rtc s3c-rtc: registered as rtc0
...
s3c-rtc s3c-rtc: setting system clock to 2023-01-01T00:55:53 UTC (1672534553)

使用ls /dev/rtc*命令就可以就找到了rtc0这个字符设备;

[root@zy:/]# ls /dev/rtc* -l    
crw-rw----    1 0        0         253,   0 Jan  1 00:55 /dev/rtc0    # rtc0为RTC设备名称
[root@zy:/]# ls -l /sys/class/rtc/                      // rtc为rtc_class类的名称
total 0
lrwxrwxrwx    1 0        0                0 Jan  1 00:55 rtc0 -> ../../devices/platform/s3c-rtc/rtc/rtc0
[root@zy:/]# ls -l /sys/bus/platform/devices/s3c-rtc/              # s3c-rtc为platform设备名称
total 0
lrwxrwxrwx    1 0        0                0 Jan  1 00:59 driver -> ../../../bus/platform/drivers/s3c-rtc
-rw-r--r--    1 0        0             4096 Jan  1 00:59 driver_override
-r--r--r--    1 0        0             4096 Jan  1 00:59 modalias
drwxr-xr-x    2 0        0                0 Jan  1 00:59 power
drwxr-xr-x    3 0        0                0 Jan  1 00:55 rtc
lrwxrwxrwx    1 0        0                0 Jan  1 00:59 subsystem -> ../../../bus/platform
-rw-r--r--    1 0        0             4096 Jan  1 00:59 uevent

在linux里有两个时钟:

  • 硬件时钟:来自于设备上的RTC芯片,通过hwclock查看;
  • 系统时钟:内核中的时钟;由linux系统软件维持的时间,通过date查看;
6.4.1 date

输入date命令查看系统时钟:

[root@zy:/]# date
Sun Jan  1 01:02:54 UTC 2023

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

比如输入:date  "+ %Y/%m/%d %H:%M:%S"

[root@zy:/]# date  "+ %Y/%m/%d %H:%M:%S"
 2023/01/01 01:04:36

date -s命令设置时间:

[root@zy:/]# date -s "2023-03-26 15:18:40"
Sun Mar 26 15:18:40 UTC 2023
6.4.2 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,即可同步硬件时钟:

[root@zy:/]# hwclock -r
Sun Jan  1 01:08:51 2023  0.000000 seconds      # 未同步之前的时间
[root@zy:/]# hwclock -w
[root@zy:/]# hwclock -r
Sun Mar 26 15:09:24 2023  0.000000 seconds      # 同步后的时间
6.4.3 查看RTC信息
[root@zy:/]# cat /proc/driver/rtc
rtc_time        : 15:16:47
rtc_date        : 2023-03-26
alrm_time       : 00:00:00          
alrm_date       : 1970-01-01        
alarm_IRQ       : no                # INT_RTC闹钟中断未开启
alrm_pending    : no
update IRQ enabled      : no
periodic IRQ enabled    : no        # INT_TICK时钟节拍中断未开启 
periodic IRQ frequency  : 1         # 时钟节拍中断频率  
max user IRQ frequency  : 128       # 时钟节拍中断最大频率
24hr            : yes
periodic_IRQ    : no

参考文章

[1]30.Linux-RTC驱动分析及使用

[2]Linux驱动| Linux内核 RTC时间架构

[3]Linux驱动修炼之道-RTC子系统框架与源码分析

[4]i.MX 6ULL 驱动开发 二十:RTC

[5]Linux RTC驱动分析及应用

[6]「驱动知识」Linux下RTC时间的读写分析

[7]linux 内核 - ioctl 函数详解

[8]最小堆定时器 —— 实现

posted @ 2023-03-16 00:01  大奥特曼打小怪兽  阅读(679)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步