专栏首页Linux内核深入分析Linux RTC驱动模型分析

Linux RTC驱动模型分析

RTC简介

RTC(real-time clock)简称实时时钟,主要作用是用来记时,产生闹钟等。RTC因为有备份电池,所以即使计算机关机掉电,也不会影响RTC记时。而RTC和系统时间(主要靠软件模拟)的区别在于,RTC会在掉电后数据不丢失,在下次启动依旧可以重新设置当前时间给计算机。而系统时间主要靠软件模拟产生,在掉电之后会丢失,需要在下次计算机重新启动之后重新模拟产生。RTC时间在每次系统启动的时候会使用,在以后需要的时候会将设置的时间写入到RTC中,别的时候获取时间都通过软件可以获得。 RTC可以使用周期性的中断来产生闹钟,也可以在系统suspend的时候作为系统的唤醒源使用。Linux系统提供了两套RTC接口,/dev/rtc是为pc机器提供,另一种/dev/rtc0, /dev/rtc1支持所有的系统,具体可参考rtc.txt文档。linux为新的接口设计一套驱动模型,如果驱动工程师想增加某一个驱动,只需要将芯片相关的代码编写,然后注册到rtc核心层中即可。

RTC驱动框架

RTC涉及的代码如下:

driver/rtc/class.c: 此文件向linux内核驱动模型注册了一个类RTC, 同时为底层的RTC驱动提供了注册/注销RTC接口。同时实现了RTC相关的PM操作。

driver/rtc/rtc-dev.c: 将各种各样的RTC设备抽象成一个字符设备,同时提供文件操作函数集。

driver/rtc/rtc-sysfs.c: 用户可以通过sysfs文件系统方便快捷的操作rtc设备。

driver/rtc/rtc-proc.c: 可以通过proc文件系统获得rtc的相关信息,比如rtc_time, rtc_data等信息。

driver/rtc/interface.c: 提供应用程序和驱动的接口函数,主要是为rtc提供相关的调用接口。

driver/rtc/rtc-lib.c: 提供了一个rtc和data以及time之间的转换函数

driver/rtc/hctosys.c: 用于开机启动的时候获取rtc的值。

driver/rtc/rtc-xxx.c: 各式各样的rtc驱动。

RTC的模型图如下:

通过上图可以清晰的看出class.c为各种各异的驱动提供了注册接口。同样用户可以操作设备节点/dev/rtc0,也可以通过sysfs或者proc文件系统最终通过interface操作到实际的驱动代码中。rtc-dev.c是对各式各样的rtc驱动的一个抽象,所以下一步先分析rtc-dev.c。

基本数据结构

在分析代码之前需要了解一些必要的数据结构

1. struct rtc-device数据结构

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

	int id;                                              //代表是那个rtc设备
	char name[RTC_DEVICE_NAME_SIZE];                     //代表rtc设备的名称

	const struct rtc_class_ops *ops;                     //rtc操作函数集,需要驱动实现
	struct mutex ops_lock;                               //操作函数集的互斥锁

	struct cdev char_dev;                                //代表rtc字符设备,因为rtc就是个字符设备
	unsigned long flags;                                 //rtc的状态标志,例如RTC_DEV_BUSY

	unsigned long irq_data;                              //rtc中断数据
	spinlock_t irq_lock;                                 //访问数据是要互斥,需要spin_lock
	wait_queue_head_t irq_queue;                         //数据查询中用到rtc队列
	struct fasync_struct *async_queue;                   //异步队列

	struct rtc_task *irq_task;                           //在中断中使用task传输数据
	spinlock_t irq_task_lock;                            //task传输互斥
	int irq_freq;                                        //rtc的中断频率
	int max_user_freq;                                   //rtc的最大中断频率

	struct timerqueue_head timerqueue;                   //定时器队列                  
	struct rtc_timer aie_timer;                          //aie(alaram interrupt enable)定时器
	struct rtc_timer uie_rtctimer;                       //uie(update interrupt enable)定时器
	struct hrtimer pie_timer; /* sub second exp, so needs hrtimer */ //pie(periodic interrupt enable)定时器
	int pie_enabled;                                     //pie使能标志
	struct work_struct irqwork;                          
	/* Some hardware can't support UIE mode */
	int uie_unsupported;                                  //uie使能标志

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL                           //RTC UIE emulation on dev interface配置项,目前没有开启
	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
};

这个结构是rtc驱动的核心结构,当驱动程序使用rtc_device_register函数传递正确的参数,然后就返回struct rtc_deivce给驱动程序。而在这个结构中rtc_class_ops函数需要驱动程序实现。

2. struct rtc_class_ops数据结构

struct rtc_class_ops {
	int (*open)(struct device *);
	void (*release)(struct device *);
	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_mmss)(struct device *, unsigned long secs);
	int (*read_callback)(struct device *, int data);
	int (*alarm_irq_enable)(struct device *, unsigned int enabled);
};

这些函数中大部分需要驱动程序实现,比如open, read_time, set_time等。这些函数大多数都是和rtc芯片的操作有关。

rtc-dev.c代码分析

rtc-dev.c是对形形色色的rtc设备进行抽象,实现一些公共的功能,然后将此抽象rtc设备注册为字符设备。

void __init rtc_dev_init(void)
{
	int err;

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

动态分配一个次设备号为0,相同设备的最大个数为16的字符设备。该函数会在rtc_init函数中被调用。

输出参数设备号rtc_devt, 由主设备号和次设备号组成。

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

	if (rtc->id >= RTC_DEV_MAX) {                                         //合法性判断,如果id大于16个,说明rtc设备个数太多
		dev_dbg(&rtc->dev, "%s: too many RTC devices\n", rtc->name);
		return;
	}

	rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);            

#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL                                          //UIE模拟配置相关,不做过多介绍
	INIT_WORK(&rtc->uie_task, rtc_uie_task);
	setup_timer(&rtc->uie_timer, rtc_uie_timer, (unsigned long)rtc);
#endif

	cdev_init(&rtc->char_dev, &rtc_dev_fops);                        //字符设备初始化,以及文件操作函数集合初始化
	rtc->char_dev.owner = rtc->owner;
}

该函数主要是初始化字符设备,设置rtc相关的file operation函数集合。

void rtc_dev_add_device(struct rtc_device *rtc)
{
	if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))
		dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n",
			rtc->name, MAJOR(rtc_devt), rtc->id);
	else
		dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name,
			MAJOR(rtc_devt), rtc->id);
}

调用cdev_add函数将rtc字符设备加入到内核中。这样以来rtc字符设备已经加入到系统中,就等待应用程序的调用。应用程序操作之前还需要实现rtc_dev_fops:

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字符设备驱动对应的file operation操作函数集合。接下来一个一个分析。

当应用程序打开/dev/rtc设备的时候就会走到open函数集合中。

static int rtc_dev_open(struct inode *inode, struct file *file)
{
	int err;
	struct rtc_device *rtc = container_of(inode->i_cdev,
					struct rtc_device, char_dev);
	const struct rtc_class_ops *ops = rtc->ops;                             //获得驱动的rtc ops

	if (test_and_set_bit_lock(RTC_DEV_BUSY, &rtc->flags))                  //检测rtc是否现在在使用,如果没有使用即可open           
		return -EBUSY;

	file->private_data = rtc;                                        //将rtc放入到private_data变量中

	err = ops->open ? ops->open(rtc->dev.parent) : 0;                //如果驱动实现open函数,就调用驱动的open,如果没有实现返回0
	if (err == 0) {
		spin_lock_irq(&rtc->irq_lock);
		rtc->irq_data = 0;
		spin_unlock_irq(&rtc->irq_lock);

		return 0;
	}

	/* something has gone wrong */
	clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);                 //离开的时候将rtc设备为不忙
	return err;
}

以上操作就是rtc的open操作,多么简单,多么熟悉的套路。

/**
 * test_and_set_bit_lock - Set a bit and return its old value, for lock
 * @nr: Bit to set
 * @addr: Address to count from
 *
 * This operation is atomic and provides acquire barrier semantics.
 * It can be used to implement bit locks.
 */
#define test_and_set_bit_lock(nr, addr)	test_and_set_bit(nr, addr)

设置一个bit然后返回以前的值, 用于检测是设备是否在使用。 接下来分析read函数的执行过程。

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;                    //从private_data域取出rtc数据,在open中设置的private_data

	DECLARE_WAITQUEUE(wait, current);                             //声明一个等待队列wait
	unsigned long data;
	ssize_t ret;

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

	add_wait_queue(&rtc->irq_queue, &wait);                       //将等待队列加入到rtc的等待队列
	do {
		__set_current_state(TASK_INTERRUPTIBLE);              //设置当前进程的状态为可中断类型

		spin_lock_irq(&rtc->irq_lock);
		data = rtc->irq_data;                                  //读取irq_date的数据,在中断中有数据的时候会设置irq_date的值
		rtc->irq_data = 0;
		spin_unlock_irq(&rtc->irq_lock);

		if (data != 0) {                                       //data不等于0,说明有数据,跳出while循环
			ret = 0;
			break;
		}
		if (file->f_flags & O_NONBLOCK) {                       //如果读取数据是非阻塞的方式,直接返回
			ret = -EAGAIN;
			break;
		}
		if (signal_pending(current)) {                         //收到信号中断,退出
			ret = -ERESTARTSYS;
			break;
		}
		schedule();                                            //调度出去,睡眠
	} while (1);
	set_current_state(TASK_RUNNING);                                //执行到这里说明是从上述3种情况break出来的,然后将进程状态设置为running
	remove_wait_queue(&rtc->irq_queue, &wait);                      //从等待队列移除wait

	if (ret == 0) {                                                 //ret等于0,说明是rtc中断触发导致退出while循环
		/* Check for any data updates */
		if (rtc->ops->read_callback)                            //驱动程序是否实现read_callback, 一般驱动程序没有实现该回调函数
			data = rtc->ops->read_callback(rtc->dev.parent,
						       data);

		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中断发生,如果有read读就不会blocked。 而此read不是用来读取具体时间的函数。

接下来分析poll函数。

static unsigned int rtc_dev_poll(struct file *file, poll_table *wait)
{
	struct rtc_device *rtc = file->private_data;
	unsigned long data;

	poll_wait(file, &rtc->irq_queue, wait);          //使用poll系统调用,一直等待有数据是否到来

	data = rtc->irq_data;

	return (data != 0) ? (POLLIN | POLLRDNORM) : 0;  //返回结果(有数据可读|有普通数据可读)
}

接下来分析rtc的重点函数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;
	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;
       //以下几个都是合法性检测,检测调用者是否有权限执行操作。
	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;

	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:                                                     //设置闹钟时间
		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;

		/* 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.
		 */
		{
			unsigned long now, then;

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

			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;
			rtc_tm_to_time(&alarm.time, &then);

			/* alarm may need to wrap into tomorrow */
			if (then < now) {
				rtc_time_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:                                                //Enable the periodic interrupt
		err = rtc_irq_set_state(rtc, NULL, 1);
		break;
	case RTC_PIE_OFF:                                               //Disable the periodic interrupt
		err = rtc_irq_set_state(rtc, NULL, 0);
		break;
	case RTC_AIE_ON:                                                //Enable the alarm interrupt
		mutex_unlock(&rtc->ops_lock);
		return rtc_alarm_irq_enable(rtc, 1);
	case RTC_AIE_OFF:                                                 //Disable the alarm interrupt
		mutex_unlock(&rtc->ops_lock);
		return rtc_alarm_irq_enable(rtc, 0);
	case RTC_UIE_ON:                                                  //Enable the interrupt on every clock update
		mutex_unlock(&rtc->ops_lock);
		return rtc_update_irq_enable(rtc, 1);
	case RTC_UIE_OFF:                                                 //Disable the interrupt on every clock update
		mutex_unlock(&rtc->ops_lock);
		return rtc_update_irq_enable(rtc, 0);
	case RTC_IRQP_SET:                                                //Set IRQ rate
		err = rtc_irq_set_freq(rtc, NULL, arg);
		break;
	case RTC_IRQP_READ:                                                 //Read IRQ rate 
		err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
		break;
	case RTC_WKALM_SET:                                                     //Set wakeup alarm
		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:                                                      //Get wakeup alarm
		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;
}

以上就是全部ioctl的操作,大多数rtc的功能都在这个函数中的case当中被调用。 接下来是rtc的关闭函数。

static int rtc_dev_release(struct inode *inode, struct file *file)
{
	struct rtc_device *rtc = file->private_data;

	/* Keep ioctl until all drivers are converted */
	rtc_dev_ioctl(file, RTC_UIE_OFF, 0);                            //关闭rtc的uie中断
	rtc_update_irq_enable(rtc, 0);                                  //disable  rtc中断
	rtc_irq_set_state(rtc, NULL, 0);

	if (rtc->ops->release)
		rtc->ops->release(rtc->dev.parent);                      //调用驱动的release函数

	clear_bit_unlock(RTC_DEV_BUSY, &rtc->flags);                  //将rtc的状态设置为空闲,也就是不忙。
	return 0;
}

以上就是全部的rtc-dev.c的分析。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux RTC驱动模型分析之rtc-proc.c

    proc下的rtc节点的位置是: /proc/driver/rtc。 该节点可以清晰的显示出当前的时间,当前的日期,alarm的时间,日期,alarm是否使能...

    DragonKingZhu
  • Linux电源管理-Linux Regulator Framework代码分析

    在内核kernel/drivers/regulator/dummy.c文件中构造了一个虚拟的regulator,参考此文件编写一个虚拟的regulator dr...

    DragonKingZhu
  • Kmalloc申请内存源码分析

    再上一节了解了SLUB是如何申请一个object的,其中涉及了从当前的freelist申请,以及kmem_cache_cpu->partital链表申请,以及到...

    DragonKingZhu
  • Linux RTC驱动模型分析之rtc-sysfs.c【转】

    转自:https://blog.csdn.net/longwang155069/article/details/52353408

    用户3033338
  • 30.Linux-RTC驱动分析及使用

     linux中的rtc驱动位于drivers/rtc下,里面包含了许多开发平台的RTC驱动,我们这里是以S3C24xx为主,所以它的RTC驱动为rtc-s3c....

    张诺谦
  • 自媒体作者评奖活动 (10月)

    为鼓励参与腾讯云自媒体计划的作者产出更多优质内容,云+社区将试运行自媒体作者月度评奖活动(以下简称活动)。

    云加社区
  • picu项目 golang使用体会

    radaren
  • Zookeeper详解(十):Pytho

    关于Watcher,网上很多帖子都是通过装饰器的方式实现的,其实我上面的方式和装饰器是一样的,只是形式不同罢了。功能都能实现,只是用装饰器有时候会不方便。

    py3study
  • python数字图像处理(12):基本图形的绘制

    skimage.draw.set_color(img, coords, color)

    bear_fish
  • 接口自动化测试平台:接口内容的简单执行

    4、前端对数据进行处理,如果是列表的批量执行,只会刷新case的最后执行状态,如果是单条case的调试执行,会渲染最新的result(接口返回结果)

    软件测试君

扫码关注云+社区

领取腾讯云代金券