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 来设置。