----------------------------------------------------------------------------------------------------------------------------
内核版本: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)
该函数内部调用了__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:%dn", 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 %sn", 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: %sn", "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: %sn", "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: %sn", "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 handledn"); } err = rtc_valid_tm(&alarm->time); done: if (err) dev_warn(&rtc->dev, "invalid alarm value: %ptRn", &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, %ptRn", 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; }
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,该函数主要做了如下操作:初始化字符设备,设置其主设备号为MAJOR(rtc_devt),这个是在模块入口函数rtc_dev_init中申请的,次设备号为rtc->id,设置字符设备的文件操作集为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 devicesn"); return; } rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); #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, 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 classn"); 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 regionn"); }
四、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); // 动态分配sec_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 tickn"); 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_datan"); 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 alarmn"); return info->irq_alarm; } dev_dbg(&pdev->dev, "s3c2410_rtc: tick irq %d, alarm irq %dn", 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 clockn"); else dev_dbg(&pdev->dev, "probe deferred due to missing rtc clkn"); 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 clockn"); else dev_dbg(&pdev->dev, "probe deferred due to missing rtc src clkn"); 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=%02xn", 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 itn"); } /* 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 rtcn"); 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 %dn", 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 %dn", 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-enablingn"); 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_CNTSELn"); 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_CLKRSTn"); 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 %ptRn", 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=%dn", 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 %ptRn", tm); /* we get around y2k by simply not supporting it */ if (year < 0 || year >= 100) { dev_err(dev, "rtc only supports 100 yearsn"); 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, %ptRn", 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 %08xn", 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=%dn", __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
参考文章
[8]最小堆定时器 —— 实现
文章来源: 博客园
- 还没有人评论,欢迎说说您的想法!