Linux RTC 驱动模型分析

RTC(实时时钟)借助电池供电,在系统掉电的情况下依然可以正常计时。它通常还具有产生周期性中断以及闹钟(Alarm)中断的能力

Linux 内核把 RTC 用作“离线”的时间与日期维护器。当 Linux 内核启动时,它从 RTC 中读取时间与日期,并在需要时将时间回写到 RTC 芯片。另外,如果 RTC 提供了 IRQ 中断并且可以定时,那么 RTC 还可以作为内核睡眠时唤醒内核的闹钟。应用程序可以用 RTC 提供的周期中断做一些周期任务。


一、RTC 驱动模型结构

RTC的驱动模型结构如下图所示:


class.c               这个文件向 Linux 设备模型核心注册一个 RTC 类,然后向驱动程序提供了注册/注销接口

rtc-dev.c 这个文件定义了基本的设备文件操作函数,如:open()、read() 等

interface.c 顾名思义,这个文件主要提供了用户程序与 RTC 驱动的接口函数,用户程序一般通过 ioctl() 与 RTC 驱动交

                        互,这里定义了每个 ioctl 命令需要的调用的函数

rtc-sysfs.c 与 sysfs 有关

rtc-proc.c 与 proc 文件系统有关


二、基本数据结构

这里涉及到 3 个主要的数据结构,分别是 struct rtc_device、struct rtc_class_ops、struct rtc_time。

1. struct rtc_device

struct rtc_device 的定义位于文件 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;

	struct rtc_task *irq_task;
	spinlock_t irq_task_lock;
	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;

	bool registered;

	struct nvmem_config *nvmem_config;
	struct nvmem_device *nvmem;
	/* Old ABI support */
	bool nvram_old_abi;
	struct bin_attribute *nvram;

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

这个结构是 RTC 驱动程序的基本数据结构,使用时不需要驱动程序提前动态申请,因为驱动程序在注册时会返回这个结构。

2. struct rtc_class_ops

struct rtc_class_ops 的定义位于文件 include/linux/rtc.h 中,如下所示:

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 驱动程序要实现的基本操作函数,注意这里的操作不是文件操作。驱动程序通过初始化这样一个结构,将自己实现的函数与 RTC 核心联系起来。这里面的大部分函数都要驱动程序来实现,而且这些函数都是操作底层硬件的,属于最底层函数。

3. struct rtc_time

struct rtc_time 的定义位于文件 include/uapi/linux/rtc.h 中,如下所示:

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

代表了日期和时间,从 RTC 设备读回的时间和日期就保存在这个结构体中。


三、class.c

1. 模块初始化函数 rtc_init()

static int __init rtc_init(void)
{
	/* 创建类 */
	rtc_class = class_create(THIS_MODULE, "rtc");
	if (IS_ERR(rtc_class)) {
		pr_err("couldn't create class\n");
		return PTR_ERR(rtc_class);
	}
	/* 初始化类成员 */
	rtc_class->pm = RTC_CLASS_DEV_PM_OPS;
	/* 初始化设备 */
	rtc_dev_init();
	return 0;
}

在 rtc_init() 中首先调用 class_create() 创建了一个类(类名为 rtc,即在 sys/class 目录下创建了一个目录 rtc),然后初始化类相关的成员 pm(在原来版本中,这里初始化的成员是 suspend 和 resume),最后调用 rtc_dev_init() 函数,这个函数的主要工作是为 RTC 动态分配设备号,并保存在 rtc_devt 中。

2. 驱动注册函数 rtc_device_register()

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 */
	id = rtc_device_get_id(dev);
	if (id < 0) {
		err = id;
		goto exit;
	}

	/* 申请rtc */
	rtc = rtc_allocate_device();
	if (!rtc) {
		err = -ENOMEM;
		goto exit_ida;
	}

	/* 初始化rtc结构体 */
	rtc->id = id;
	rtc->ops = ops;										/* 操作结构体 */
	rtc->owner = owner;
	rtc->dev.parent = dev;

	/* 设置设备名称 */
	dev_set_name(&rtc->dev, "rtc%d", id);

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

	/* 添加字符设备 */
	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);
	}

	/* proc文件系统相关 */
	rtc_proc_add_device(rtc);

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

	/* 返回rtc结构体 */
	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);
}

(1)获取 id:id 相关的知识百度

(2)通过 rtc_allocate_device() 函数动态申请 struct rtc_device,并初始化其成员。其中在 rtc_allocate_device() 函数中已初始化了一部分成员,其中最主要的是类属性的赋值。

(3)通过调用 rtc_dev_prepare() 函数在注册字符设备前做一些准备工作。其实这里所做的准备工作就是计算设备号和初始化字符设备。

(4)通过 cdev_device_add() 函数注册字符设备。

(5)proc 文件系统相关操作

注意:该函数返回的是 struct rtc_device 的指针。


四、rtc-dev.c

在 rtc-dev.c 文件中,初始化了一个 file_operations 结构体-- rtc_dev_fops,并定义了这些操作函数。

1. file_operations 结构体-- 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,
};

2. 函数的实现(略)


五、interface.c

interface.c 文件中的所有函数的实现都对应于 rtc-dev.c 中 ioctl 相应的命令,对应关系如下:

RTC_ALM_READ 				rtc_read_alarm					/* 读取闹钟 */
RTC_ALM_SET 				rtc_set_alarm					/* 设置闹钟 */
RTC_RD_TIME 				rtc_read_time					/* 读取时间 */
RTC_SET_TIME 				rtc_set_time					/* 设置时间 */
RTC_PIE_ON RTC_PIE_OFF 		rtc_irq_set_state				/* 开/关RTC全局中断 */
RTC_AIE_ON RTC_AIE_OFF 		rtc_alarm_irq_enable			/* 使能/禁止RTC闹钟中断 */
RTC_UIE_ON RTC_UIE_OFF 		rtc_update_irq_enable			/* 使能/禁止RTC更新中断 */
RTC_IRQP_SET				rtc_irq_set_freq				/* 设置中断频率 */

以上就是所有 ioctl 的命令与实现的对应关系,其中如果不涉及中断的话,有两个命令需要我们特别关心一下:RTC_RD_TIME 和 RTC_SET_TIME。因为 RTC 最基本的功能就是提供时间,而这两个命令恰恰是读取和设置时间。下面分析一下这两个命令,也就是这两个命令对应的函数。

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

		err = rtc_valid_tm(tm);
		if (err < 0)
			dev_dbg(&rtc->dev, "read_time: rtc_time isn't valid\n");
	}
	return err;
}
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);									/* 解锁 */
	return err;
}

在 rtc_read_time() 函数中使用了互斥锁来保证同一时刻只能有一个进程操作 RTC,然后通过调用 __rtc_read_time() 函数,实现真正的时间读取;在 __rtc_read_time() 函数中通过 rtc->ops->read_time() 这一步骤调用底层驱动实现的读取时间函数。

2. rtc_set_time()

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 = 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 if (rtc->ops->set_mmss64) {
		time64_t secs64 = rtc_tm_to_time64(tm);

		err = rtc->ops->set_mmss64(rtc->dev.parent, secs64);
	} else if (rtc->ops->set_mmss) {
		time64_t secs64 = rtc_tm_to_time64(tm);
		err = rtc->ops->set_mmss(rtc->dev.parent, secs64);
	} else
		err = -EINVAL;

	pm_stay_awake(rtc->dev.parent);
	mutex_unlock(&rtc->ops_lock);									/* 解锁 */
	/* A timer might have just expired */
	schedule_work(&rtc->irqwork);
	return err;
}

这个函数的实现其实同 rtc_read_time() 函数的实现相同,都是先上锁然后调用底层驱动函数。但是这里设置时间的函数提供了三个:set_time、set_mmss64、set_mmss。因为不同的 RTC 硬件操作可能不同,比如有的 RTC 硬件只计算秒数,不关心时间,这样的 RTC 就通过 set_mmss 或 set_mmss64 来设置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值