Linux驱动开发项目BL5372--linux下RTC框架的使用笔记

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134388342

一、开发环境

linux 4.19

二、RTC框架的基本用法

在看正点原子的linux驱动开发教程的时候,因为正点说大部分厂商都把rtc驱动写好了,不用自己写,所以讲的比较粗略,大致说了下RTC框架的基本用法以及如果有必要自己写RTC驱动时,使用者要做的两件事:

2.1 编写RTC设备的最底层操作函数集合

编写RTC设备的最底层操作函数集合,也就是rtc_class_ops结构体
在这里先看一下struct rtc_class_ops的定义:

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 (*set_mmss64)(struct device *, time64_t secs);
	int (*set_mmss)(struct device *, unsigned long secs);
	int (*read_callback)(struct device *, int data);
	int (*alarm_irq_enable)(struct device *, unsigned int enabled);
	int (*read_offset)(struct device *, long *offset);
	int (*set_offset)(struct device *, long offset);
};

我们要做的就是创建一个rtc_class_ops变量,并将里面的函数指针指向我们驱动程序中自己编写的函数,如下:

...
int my_read_time(struct device *, struct rtc_time *)
{
	....
}
...
struct rtc_class_ops my_ops = {
	.read_time = my_read_time,
	....
}
2.2 使用rtc_device_register()函数来向系统注册RTC设备

但是我并不清楚rtc_device_register()函数做了什么事情,为什么向系统注册了设备之后,就可以控制设备了呢?在我另一篇纹章解析hwclock命令的时候说过,hwclock这个命令其实是打开了"/dev/rtc" ,"/dev/rtc0", "/dev/misc/rtc"这三个设备的其中一个,然后用户层使用ioctl()函数来下达命令,驱动程序根据传入的命令控制对应的寄存器实现相应的功能。所以RTC设备本质上是一个字符设备。字符设备肯定有字符设备操作集,但是我很好奇这个字符设备操作集在哪儿绑定的呢,所以就看了下rtc_device_register()这个函数

三、rtc_device_register()函数解析

先放上这个函数的源码,此函数位于drivers/rtc/class.c中。传入的四个参数及类型分别为:

  • const char *name, 设备名字
  • struct device *dev, 设备,在后面分析过程中,可以看到这个设备其实是要注册的RTC设备的父设备
  • const struct rtc_class_ops *ops, 即RTC 底层驱动函数集。
  • struct module *owner, 驱动模块拥有者,一般为THIS_MODULE
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
					const struct rtc_class_ops *ops,
					struct module *owner)
{
	struct rtc_device *rtc;
	struct rtc_wkalrm alrm;
	int id, err;

	id = rtc_device_get_id(dev);
	if (id < 0) {
		err = id;
		goto exit;
	}

	rtc = rtc_allocate_device();
	if (!rtc) {
		err = -ENOMEM;
		goto exit_ida;
	}

	rtc->id = id;
	rtc->ops = ops;
	rtc->owner = owner;
	rtc->dev.parent = dev;

	dev_set_name(&rtc->dev, "rtc%d", id);

	rtc_device_get_offset(rtc);

	/* Check to see if there is an ALARM already set in hw */
	err = __rtc_read_alarm(rtc, &alrm);

	if (!err && !rtc_valid_tm(&alrm.time))
		rtc_initialize_alarm(rtc, &alrm);

	rtc_dev_prepare(rtc);				/* 这里执行cdev_init */

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

		/* This will free both memory and the ID */
		put_device(&rtc->dev);
		goto exit;
	} else {
		dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", name,
			MAJOR(rtc->dev.devt), rtc->id);
	}

	rtc_proc_add_device(rtc);

	dev_info(dev, "rtc core: registered %s as %s\n",
			name, dev_name(&rtc->dev));

	return rtc;

exit_ida:
	ida_simple_remove(&rtc_ida, id);

exit:
	dev_err(dev, "rtc core: unable to register %s, err = %d\n",
			name, err);
	return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(rtc_device_register);

该函数创建了一个struct rtc_device类型的指针,并且该函数最终返回的也是这个指针,这个结构体定义在include/linux/rtc.h中,其中成员变量众多,我只把跟rtc_device_register()函数有关的一些变量贴上来::

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;
	...
	spinlock_t irq_lock;
	...
};

根据该函数的源码中的这几行代码:

	rtc->id = id;
	rtc->ops = ops;
	rtc->owner = owner;
	rtc->dev.parent = dev;

通过rtc->ops = ops可以看出把rtc的ops绑定到了我们传入的ops上,同时把rtc下的struct device类型的成员变量dev的父设备绑定到了我们传入的dev上。这里知道了传入的dev其实只是为了给要注册的rtc设备绑定一个父设备,所以按道理随便传入一个无关紧要的父设备就可以了,但是后续我在编写BL5372这个rtc芯片的驱动程序的时候这里出了问题(BUG记录在我的另外一篇文章)。但是现在还是没有看懂用户层在打开字符设备后是如何调用到struct rtc_class_ops *ops上的。

继续往下看,下面这段代码看命名貌似是与RTC的闹钟有关,暂时先不考虑(后续会回头再看的)

	err = __rtc_read_alarm(rtc, &alrm);

	if (!err && !rtc_valid_tm(&alrm.time))
		rtc_initialize_alarm(rtc, &alrm);

接着重点看一下rtc_dev_prepare()这个函数,这个函数是理解RTC框架的关键,这个函数定义在drivers/rtc/rtc-dev.c中,其源码如下:

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

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
	INIT_WORK(&rtc->uie_task, rtc_uie_task);
	timer_setup(&rtc->uie_timer, rtc_uie_timer, 0);
#endif

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

从这个函数中可以看到注册字符设备很多熟悉的地方,比如MKDEV创建设备号,cdev_init()等等。从cdev_init()可以看出,rtc->char_dev这个字符设备的操作集其实是rtc_dev_fops,这个操作集就在同一个文件下(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,
};

到这里算是知道hwclock(以及其他用户层程序)是在哪一步通过系统调用来对底层驱动进行操作了,但是到这里还不知道与我们编写的编写RTC设备的最底层操作函数集合rtc_class_ops有什么关系。在使用hwclock中,使用ioctl()函数来传递指令,那我们就来看一看字符操作集中的.unlocked_ioctl = rtc_dev_ioctlrtc_dev_ioctl这个函数也在同一个文件,其源码较长,有兴趣的可以自己去看一下,这里为了有针对性的解释如何调用我们自己写的RTC底层操作函数集,简单的摘录一部分如下:

static long rtc_dev_ioctl(struct file *file,
		unsigned int cmd, unsigned long arg)
{
	int err = 0;
	struct rtc_device *rtc = file->private_data;
	const struct rtc_class_ops *ops = rtc->ops;
	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;

	...
	}

	if (err)
		goto done;

	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:
		...
	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);

	...

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

以RTC_RD_TIME为例:

	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;

如果用户层调用ioctl传入了RTC_RD_TIME,会执行rtc_read_time()函数,该函数在drivers/rtc/interface.c下,源码如下:

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

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

	err = __rtc_read_time(rtc, tm);
	mutex_unlock(&rtc->ops_lock);

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

该函数中调用了__rtc_read_time()函数,该函数位于同一文件下,其源码为:

static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{
	int err;
	if (!rtc->ops)
		err = -ENODEV;
	else if (!rtc->ops->read_time)
		err = -EINVAL;
	else {
		memset(tm, 0, sizeof(struct rtc_time));
		err = rtc->ops->read_time(rtc->dev.parent, tm);
		if (err < 0) {
			dev_dbg(&rtc->dev, "read_time: fail to read: %d\n",
				err);
			return err;
		}

		rtc_add_offset(rtc, tm);

		err = rtc_valid_tm(tm);
		if (err < 0)
			dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");
	}
	return err;
}

可见,如果你在你自己的驱动程序中令rtc->ops->read_time指向了一个函数后(!rtc->ops->read_time,即该函数指针不为NULL),则会最终调用rtc->ops->read_time指向的函数,也就是自己驱动程序中编写的readtime函数。而你需要做的就是在自己的readtime函数中对硬件rtc的寄存器进行操作即可。

其余函数如设置硬件rtc时间也大致如此,可以通过用户层使用ioctl()函数传入来最终调用我们绑定的RTC设备的最底层操作函数。这就是rtc_device_register()这个函数的部分作用。提一嘴的是,我看了看drivers/rtc/下的一些其他已经编写好的rtc芯片的驱动程序时,它们更常用的是devm_rtc_device_register()这个函数。该函数源码如下:

struct rtc_device *devm_rtc_device_register(struct device *dev,
					const char *name,
					const struct rtc_class_ops *ops,
					struct module *owner)
{
	struct rtc_device **ptr, *rtc;

	ptr = devres_alloc(devm_rtc_device_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return ERR_PTR(-ENOMEM);

	rtc = rtc_device_register(name, dev, ops, owner);
	if (!IS_ERR(rtc)) {
		*ptr = rtc;
		devres_add(dev, ptr);
	} else {
		devres_free(ptr);
	}

	return rtc;
}

可见该函数最终也是调用了rtc_device_register(),而devres_alloc()devres_add()等函数的作用这篇博客中做了详细的解释.。

但是有一些对于硬件RTC的功能的实现在Linux的RTC框架下更加复杂,比如设置闹钟和读取闹钟,所以最终我放弃了使用RTC框架,转而直接将这个rtc注册成了MISC设备。原因在下一节中进行了说明。

四、新的问题

其实看到上面那步已经可以实现读硬件RTC时间和写硬件RTC时间功能了,如果不需要其他功能的话就可以使用了。如果需要其他功能,我认为使用RTC框架不如直接注册成MISC设备或者普通的字符设备来读写相应的寄存器来的轻松,因为并不是所有的RTC硬件都能适配linux下的RTC框架,下面的笔记可以不用看了。

4.1 设置闹钟

我选用的RTC硬件需要启用闹钟功能,以便在CPU休眠时定时闹钟到时候唤醒CPU。所以继续看一下RTC框架是如何设置RTC硬件闹钟的,没想到学到的东西是越来越多。我们先看一下当ioctl()传入RTC_ALM_SET时执行的代码:

	switch (cmd) {
	...
	case RTC_ALM_SET:
		mutex_unlock(&rtc->ops_lock);

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

		alarm.enabled = 0;
		alarm.pending = 0;
		alarm.time.tm_wday = -1;
		alarm.time.tm_yday = -1;
		alarm.time.tm_isdst = -1;

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

这里引出了一个新的结构体struct rtc_wkalrm,即代码中alarm的参数类型:

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: 是否启用闹钟
  • pending:闹钟是否处于活动状态
  • time:要设置的闹钟时间

在进入这个case之后,根据传入的参数uargalram.time进行了初始化。并对传入时间进行了规范性检查(检查传入的时间是否符合linux的时间设置要求)。如果检查无误,则调用rtc_set_alarm()函数,该函数位于drivers/rtc/interface.c中,其源码如下:

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);  /* 这个并不会检查weekday,所以可以绕过这个 */
	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.enabled在rtc_init_timer的时候设为0了,但是会在rtc_initialize_alarm中再次开启 */
		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);	/* 这里面会把aie_timer.enabled置1 */
	mutex_unlock(&rtc->ops_lock);
	return err;
}

这里涉及到两个重要的函数:rtc_timer_remove()rtc_timer_enqueue(),这两个函数又牵扯出了linux下的另外一个子系统。但是到此为止没有回头路了,索性就继续看下了。首先这里需要知道struct rtc_device(即我们使用rtc_device_register()函数注册返回的rtc设备的类型)里面到底有哪些成员变量,结构体定义如下:

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;		/* 维护了一个hrtimer红黑树,给闹钟用的*/
	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;

	struct nvmem_device *nvmem;
	/* 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类型;

其中rtc_set_alarm()中出现的aie_timer是个struct rtc_timer类型,其定义如下:

struct rtc_timer {
	struct timerqueue_node node;			/* 而这个timerqueue_node结构体里面的expires就是硬超时时间 */
	ktime_t period;							/* 如果高精度定时器都是通用的话,这个就是软超时时间 */
	void (*func)(void *private_data);		/* 定时器到时的时候调用的回调函数 */
	void *private_data;
	int enabled;							/* 是否使能定时器 */
};

这里就涉及到linux下的另外一个子系统:时间子系统的一些机制,我看了看CSDN和知乎上的一些相关文章,认为这几篇文章写的比较好,如果想研究的话值得一看:

但是想要深入研究我觉得还是应该挑一本讲解Linux内核的书看一看,更加系统的认识Linux内核。言归正传,从strcut rtc_timer下的变量struct timerqueue_node node以及struct rtc_device下的变量struct timerqueue_head timerqueue来看,其实rtc设备结构体下维护了一个高精度定时器队列,该队列使用红黑树来实现。继续看rtc_set_alarm()的代码的这一段:

int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
	...
	if (rtc->aie_timer.enabled)						/* rtc->aie_timer.enabled在rtc_init_timer的时候设为0了,但是会在rtc_initialize_alarm中再次开启 */
		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);	/* 这里面会把aie_timer.enabled置1 */
	...
}

第一个if判断,如果rtc->aie_timer.enabled使能了,则会调用rtc_timer_remove()函数,从定时器队列中删除上一个闹钟,也就是aie_timer表示的那个闹钟。然后修改aie_timerexpiresperiod的值(定时器中的概念,想了解的可以看看我列出来的那三篇文章)。后面第二个if判断,如果传入的alarm->enabled使能了,则调用rtc_timer_enqueue()函数将修改后的aie_timer重新加入到定时器队列中。

到这里好像发现,对alarm的操作好像跟read_time和set_time不一样?甚至都跟我们自己定义的RTC底层设备操作集中的设置闹钟的函数没什么关联?别急,看一下rtc_timer_remove()rtc_timer_enqueue()函数,这俩函数位于drivers/rtc/interface.c下,先来看一下先看rtc_timer_remove()函数:

static void rtc_timer_remove(struct rtc_device *rtc, struct rtc_timer *timer)
{
	struct timerqueue_node *next = timerqueue_getnext(&rtc->timerqueue);
	timerqueue_del(&rtc->timerqueue, &timer->node);
	trace_rtc_timer_dequeue(timer);
	timer->enabled = 0;
	if (next == &timer->node) {							/* 设置下一个合适的闹钟 */
		struct rtc_wkalrm alarm;
		int err;
		next = timerqueue_getnext(&rtc->timerqueue);
		if (!next) {
			rtc_alarm_disable(rtc);
			return;
		}
		alarm.time = rtc_ktime_to_tm(next->expires);
		alarm.enabled = 1;
		err = __rtc_set_alarm(rtc, &alarm);
		if (err == -ETIME) {
			pm_stay_awake(rtc->dev.parent);
			schedule_work(&rtc->irqwork);
		}
	}
}

其中timerqueue_getnext()这个函数的作用是获取定时器队列最快要到时的node,而且因为高精度度定时器的定时器队列使用了红黑树,所以根据红黑树的特性可知,就是在获取红黑树的最左下方的节点,并保存到函数中的next变量。这个函数的具体实现我并不没有去深入了解(大致看了一下,rtc->timerqueue的数据类型为timerqueue_head结构体,该结构体下有一个叫rb_root_cached结构体,这个结构体中有一个成员变量叫rb_leftmost,看名字非常直白,最左边的,所以这个变量应该是实时维护的)。

接下来执行了timerqueue_del()将当前的aie_timer从定时器队列中删除了(想想也很合理,你要设置一个新的RTC闹钟,要把上一个设置的给删掉)。如果删除的节点正好是红黑树最左下角的节点,会同时更新红黑树,并找到新的rb_leftmost

如果你删掉的节点就是上一个next所指的节点的话(if判断的事情),就需要重新设置闹钟事件,再次调用timerqueue_getnext()找到最快要到期的定时器,并调用__rtc_set_alarm()函数,设置硬件rtc的闹钟为新的next所记录的时间。而这个函数,最终就调用到了我们在RTC底层函数操作集中绑定的我们自己写的set_alarm函数来读写硬件rtc的闹钟寄存器,这个函数同样位于interface.c中,代码如下所示:

static int __rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
	...
	if (!rtc->ops)
		err = -ENODEV;
	else if (!rtc->ops->set_alarm)
		err = -EINVAL;
	else
		err = rtc->ops->set_alarm(rtc->dev.parent, alarm);
	...
	return err;
}

可以看到,最终调用了rtc->ops->set_alarm()。接下来看一下rtc_timer_enqueue()函数:

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)			/* 找到一个比现在的expires大的定时器,也就是还没过期的定时器 */
			break;
		next = timerqueue_iterate_next(next);
	}

	timerqueue_add(&rtc->timerqueue, &timer->node);
	trace_rtc_timer_enqueue(timer);
	if (!next || ktime_before(timer->node.expires, next->expires)) { /* 如果next为空,或者要设置的闹钟到期时间比next的到期时间早(此时的next表示的是一个比当前时间晚的定时器) */
		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;
}

其实就是做了与remove函数相反的事,向定时器队列中添加了一个新的节点。但是这个if判断就很怪,只有当next为空(也就是说没找到晚于当前时间的定时器,意味着定时器队列中的定时器全部过期了),或者要设置的闹钟到期时间比next的到期时间早(此时的next表示的是比当前时间晚的所有定时器中最早的一个),才会调用__rtc_set_alarm()来最终设置硬件rtc的闹钟寄存器。

那意思就是说,我虽然在rtc系统下的定时器队列中维护了你要设置的闹钟,但是我不一定把你设到rtc硬件寄存器中去(当要设置的闹钟晚于现在的next时就会出现这种情况)?这里我还没有想明白,后续花时间再梳理一下。到这里如何通过ioctl()来最终调用到我们自己写的set_alram就讲完了,而想调用到我们自己写的read_alram,则有另外一种路子。

4.2 读取闹钟

读取闹钟又做了什么事情呢?我们看一下当ioctl()传入RTC_ALM_READ后执行的代码:

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

接下来看一下rtc_read_alarm()函数,该函数同样位于interface.c中:

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 == NULL)
		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;
}

直接将rtc->aie_timer.node.expires(在rtc_set_alarm时对expires变量做了设置,使其表示上次设置的闹钟到时时间)转化了一下返回了…这好像根本没用到我们自己写的read_alam啊。于是在整个rtc文件夹下搜索了一下,总算找到了在哪里调用了我们自己写的read_alarm,其实他就在我们注册RTC设备时调用的rtc_device_register()函数中:

struct rtc_device *rtc_device_register(const char *name, struct device *dev,
					const struct rtc_class_ops *ops,
					struct module *owner)
{
	...
	rtc = rtc_allocate_device();		/* 这个函数做了很多东西,有必要看一下,基本上rtc下的所有变量都在这里进行了定义 */
	...
	/* Check to see if there is an ALARM already set in hw */
	err = __rtc_read_alarm(rtc, &alrm);			/* 这里关键,最终联系到咱们自己的read_alarm */

	if (!err && !rtc_valid_tm(&alrm.time))
		rtc_initialize_alarm(rtc, &alrm);		

	rtc_dev_prepare(rtc);
	...
}

__rtc_read_alarm()前面这俩横线就像那样,因为前面的什么__rtc_read_time之类的带横线的最后都调用了我们自己写的函数。那就看一下__rtc_read_alarm,这个函数比较长,但是大部分代码都是在做一些检验时间合理性,补充缺失值的工作,所以我只列出部分代码,完整源码同样位于interface.中:

int __rtc_read_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
	...
	/* Get the "before" timestamp */
	err = rtc_read_time(rtc, &before);
	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);		/* 这里面最终调用了我们自己的read_alarm */
		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);	/* 双重检验,看是否执行代码的时候时间发生了跳变,如果发生了跳变则重新进入循环 */
		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);
	...
	...
	 
}

其中的重点函数是rtc_read_alarm_internal(),其代码如下:

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

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

	if (rtc->ops == NULL)
		err = -ENODEV;
	else if (!rtc->ops->read_alarm)
		err = -EINVAL;
	else {
		alarm->enabled = 0;
		alarm->pending = 0;
		alarm->time.tm_sec = -1;
		alarm->time.tm_min = -1;
		alarm->time.tm_hour = -1;
		alarm->time.tm_mday = -1;
		alarm->time.tm_mon = -1;
		alarm->time.tm_year = -1;
		alarm->time.tm_wday = -1;
		alarm->time.tm_yday = -1;
		alarm->time.tm_isdst = -1;
		err = rtc->ops->read_alarm(rtc->dev.parent, alarm);
	}

	mutex_unlock(&rtc->ops_lock);

	trace_rtc_read_alarm(rtc_tm_to_time64(&alarm->time), err);
	return err;
}

可以看到,这里最终调用了rtc->ops->read_alram(),也就是我们自己写的read_alram函数。

五、rtc_device_register()其余函数内容解析

在rtc_set_alram()函数中,有两个if判断:

int rtc_set_alarm(struct rtc_device *rtc, struct rtc_wkalrm *alarm)
{
	...
	if (rtc->aie_timer.enabled)						
		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);	
	...
}

也就是说,当rtc->aie_timer.enabledalarm->enabled都使能时,才会执行下面的rtc_timer_remove()rtc_timer_enqueue()函数,那它们是在哪里使能的呢?再回头看一下rtc_device_register()函数中的另外两个函数rtc_allocate_device()rtc_initialize_alarm(),先看一下rtc_allocate_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->max_user_freq = 64;
	rtc->dev.class = rtc_class;
	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);
	/* Init aie timer */
	rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc);	/* 给aie_timer绑定了到期之后的处理函数 */
	/* Init uie timer */
	rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);
	/* Init pie timer */
	hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);	/* pie_timer是个周期定时器 */
	rtc->pie_timer.function = rtc_pie_update_irq;						/* 给周期定时器绑定了处理函数 */
	rtc->pie_enabled = 0;

	return rtc;
}

这个函数做的工作看上去比较清晰:

  • 给rtc结构体分配内存
  • 初始化rtc下的dev设备结构体
  • 初始化rtc下的定时器队列以及各种定时器,给它们绑定回调函数,也就是定时器到期时要执行的函数

所以说该函数就是做了一个初始化的工作,那么再看一下rtc_initialize_alarm()函数:

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) <					/* 在前面已经使能了alarm->enabled(如果在自己的read_alarm中使能了的话) */
			 rtc->aie_timer.node.expires)) {

		rtc->aie_timer.enabled = 1;									/* 这里使能了aie_timer */
		timerqueue_add(&rtc->timerqueue, &rtc->aie_timer.node);
		trace_rtc_timer_enqueue(&rtc->aie_timer);
	}
	mutex_unlock(&rtc->ops_lock);
	return err;
}

首先要知道,这个函数的调用是在__rtc_read_alarm()函数之后,所以传入的alarm变量已经在你自己编写的read_alarm(__rtc_read_alarm()最终会调用我们自己写的read_alarm)中做了修改。所以可以看到,如果你在我们自己写的read_alarm中对alarm->enabled进行了使能的话,这个函数将会开启rtc->aie_timer.enabled的使能,设置一次闹钟,并加入到了定时器队列中。至此,两个使能就都开启了,所以我们的read_alarm还是很重要的,要根据你自己的硬件rtc的控制寄存器判断是否开启闹钟,即是否使能alarm->enabled

rtc_device_register()函数中还有一部分是与字符设备相关的代码,这一部分大家应该比较熟悉。但这里我遇到了一个BUG,并在解决过程中学习了linux设备模型的一些知识,BUG的解决过程在我的另一篇博客中(还在编写)。

题外话

我的RTC芯片有不止一个闹钟,可以设置很多个闹钟中断,而且还有很多个乱七八糟的寄存器,还有很多个看乱七八糟的寄存器,其实跟linux下的RTC框架不是完全适配的,我觉得用这个RTC框架实在是太麻烦了,所以最终决定放弃使用RTC框架了。不如直接写寄存器来的方便。而且RTC框架中与时间子系统也有关系,我看的比较快,可能会有理解出错的地方,希望老哥们能指点一下。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值