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框架中与时间子系统也有关系,我看的比较快,可能会有理解出错的地方,希望老哥们能指点一下。

  • 17
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: bl5372 rtc i2c 驱动是指一种用于控制实时时钟的设备驱动程序。BL5372是实时时钟芯片的型号,它使用i2c总线进行通信。 i2c(Inter-Integrated Circuit)是一种用于芯片间通信的串行总线协议,能够在多个设备之间传输数据。通过i2c总线,实时时钟芯片可以与主控制器进行通信,实现时间数据的读取和修改。 bl5372 rtc i2c 驱动的作用是提供一组API接口,使得主控制器可以方便地调用这些接口来读取和设置实时时钟的时间信息。驱动程序会负责与实时时钟芯片进行通信,将读取到的时间数据返回给主控制器,或者将主控制器传递过来的时间信息写入实时时钟芯片。 bl5372 rtc i2c 驱动通常需要在嵌入式系统或其他使用实时时钟芯片的场景中使用。主控制器可以通过i2c总线与实时时钟芯片进行通信,并利用驱动程序提供的接口来操作实时时钟。 总的来说,bl5372 rtc i2c 驱动在嵌入式系统中起到了关键的作用,它简化了主控制器与实时时钟芯片的通信过程,提供了方便的接口供主控制器进行时间数据的读写操作。 ### 回答2: BL5372是一种I2C总线RTC(Real-Time Clock)驱动芯片。RTC芯片是一种在电脑或嵌入式系统中用来提供实时时间和日期功能的芯片。 BL5372 RTC I2C驱动允许主机通过I2C总线与BL5372芯片进行通信。I2C总线是一种串行通信协议,它允许多个设备共享同一条总线,在同一时刻进行数据通信。通过I2C总线,主机可以向BL5372芯片发送命令和读取返回的数据。 BL5372芯片内置了一个实时时钟电路,它可以独立工作且不会受到主机系统时间的影响。它可以提供精准的时间和日期信息,在电源断电时也可以继续工作。主机可以使用BL5372 RTC I2C驱动通过I2C总线与BL5372芯片通信,设置或读取当前时间和日期,以及其他相关功能。 BL5372 RTC I2C驱动通常由硬件和软件两部分组成。硬件部分包括I2C总线和BL5372芯片的连接,主机通过I2C总线与芯片进行数据交换。软件部分则是主机系统中的设备驱动程序,它提供了访问BL5372芯片的接口和命令集,允许主机通过I2C总线与芯片进行通信。 BL5372 RTC I2C驱动使用可以广泛应用于各种需要实时时钟功能的设备,比如计算机、嵌入式系统、物联网设备等。它可以方便主机通过I2C总线与BL5372芯片进行通信,实现精准的时间和日期功能,为各种应用提供准确的时间戳和时间同步功能。 ### 回答3: bl5372 rtc i2c 驱动是针对BL5372芯片使用I2C通信协议进行实时时钟驱动的软件程序。BL5372是一款集成了RTC(实时时钟)功能的芯片,可以用于记录时间和日期。 I2C(Inter-Integrated Circuit)是一种常用的串行通信协议,适用于短距离、低速率的通信。它使用两根线路:SDA(数据线)和SCL(时钟线)。 bl5372 rtc i2c驱动程序的主要功能是通过I2C总线和BL5372芯片进行通信,以读取和写入实时时钟相关的信息。它可以用来设置和读取当前的日期和时间,包括年份、月份、日期、小时、分钟和秒数。同时,它还可以设置闹钟功能,使得芯片能够根据预设的时间触发相应的动作。 通过bl5372 rtc i2c驱动,用户可以方便地控制BL5372芯片的实时时钟功能,实现各种时间相关的应用,比如日历、时钟、计时器等。例如,用户可以使用驱动来设置一个闹钟,当到达预设的时间时,芯片会触发一个中断信号,从而实现提醒功能。 总之,bl5372 rtc i2c驱动程序是为了方便用户对BL5372芯片的实时时钟功能进行控制而设计的,通过使用I2C通信协议,用户可以设置和读取时间信息,实现各种时间相关的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值