imx6ull-驱动开发篇40——Linux RTC 驱动简介
目录
Linux 内核 RTC 驱动
rtc_device 结构体
rtc_class_ops结构体
file_operations 函数操作集
rtc_dev_ioctl 函数
__rtc_read_time 函数
rtc_device_register函数
rtc_device_unregister 函数
RTC 也就是实时时钟,用于记录当前系统时间。
Linux 内核 RTC 驱动
RTC 设备驱动是一个标准的字符设备驱动,应用程序通过 open、 release、 read、 write 和 ioctl等函数完成对 RTC 设备的操作。
Linux 内核将 RTC 设备抽象为 rtc_device 结构体,因此 RTC 设备驱动就是申请并初始化rtc_device,最后将 rtc_device 注册到 Linux 内核里面。
rtc_device 结构体
rtc_device 结构体,定义在 include/linux/rtc.h 文件中,结构体内容如下(删除条件编译):
struct rtc_device {struct device dev; /* 继承自设备基类,用于设备模型 */struct module *owner; /* 指向拥有该设备的模块 */int id; /* RTC设备唯一标识符 */char name[RTC_DEVICE_NAME_SIZE]; /* 设备名称字符串 */const struct rtc_class_ops *ops; /* 底层硬件操作函数集(如读写时间、闹钟等) */struct mutex ops_lock; /* 保护ops操作的互斥锁 */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; /* 保护irq_task的自旋锁 */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; /* 高精度定时器(用于亚秒级事件) */int pie_enabled; /* 周期中断使能标志 */struct work_struct irqwork; /* 中断工作队列 *//* 硬件特性标志 */int uie_unsupported; /* 标记硬件是否不支持UIE(更新中断)模式 *//* ... 其他省略的成员 ... */
};
其中,ops 成员变量,是一个 rtc_class_ops 类型的指针变量, rtc_class_ops为 RTC 设备的最底层操作函数集合,包括从 RTC 设备中读取时间、向 RTC 设备写入新的时间值等。
rtc_class_ops结构体
rtc_class_ops 是需要用户根据所使用的 RTC 设备编写的,定义在include/linux/rtc.h 文件中,内容如下:
struct rtc_class_ops {/* 设备打开/关闭 */int (*open)(struct device *); // 打开RTC设备时调用void (*release)(struct device *); // 关闭RTC设备时调用/* 设备控制接口 */int (*ioctl)(struct device *, unsigned int, unsigned long); // 设备专属ioctl操作/* 时间操作 */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 *); // 输出/proc信息(调试用)/* 时间设置(两种精度) */int (*set_mmss64)(struct device *, time64_t secs); // 64位秒级时间设置int (*set_mmss)(struct device *, unsigned long secs); // 传统32位秒级时间设置(已过时)/* 中断回调 */int (*read_callback)(struct device *, int data); // 中断事件回调处理/* 中断控制 */int (*alarm_irq_enable)(struct device *, unsigned int enabled); // 闹钟中断使能开关
};
rtc_class_ops 中的这些函数只是最底层的 RTC 设备操作函数,并不是提供给应用层的file_operations 函数操作集。
file_operations 函数操作集
Linux 内核提供了一个 RTC 通用字符设备驱动文件,文件名为 drivers/rtc/rtc-dev.c, rtcdev.c 文件提供了所有 RTC 设备共用的 file_operations 函数操作集,如下所示:
static const struct file_operations rtc_dev_fops = {.owner = THIS_MODULE, // 指向拥有此结构的模块(防止模块卸载时设备正在使用).llseek = no_llseek, // 禁用文件定位(RTC设备不支持seek操作)/* 核心操作函数 */.read = rtc_dev_read, // 实现read()系统调用(读取时间/闹钟等数据).poll = rtc_dev_poll, // 实现poll()/select()系统调用(监控中断事件).unlocked_ioctl = rtc_dev_ioctl, // 无锁ioctl操作(处理RTC专属命令)/* 文件生命周期管理 */.open = rtc_dev_open, // 设备打开时调用(增加引用计数等).release = rtc_dev_release, // 设备关闭时调用(清理资源)/* 异步通知机制 */.fasync = rtc_dev_fasync, // 实现异步通知(用于信号驱动I/O)
};
应用程序可以通过 ioctl 函数来设置/读取时间、设置/读取闹钟的操作,那么对应的 rtc_dev_ioctl 函数就会执行, rtc_dev_ioctl 最终会通过操作 rtc_class_ops 中的 read_time、 set_time 等函数来对具体 RTC 设备的读写操作。
rtc_dev_ioctl 函数
rtc_dev_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; // 获取关联的RTC设备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;/* 处理标准RTC命令 */switch (cmd) {/* 读取RTC时间 */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;/* 设置RTC时间 */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:/* 尝试调用驱动特定的ioctl操作 */if (ops->ioctl) {err = ops->ioctl(rtc->dev.parent, cmd, arg);if (err == -ENOIOCTLCMD) // 驱动不支持此命令err = -ENOTTY;} else {err = -ENOTTY; // 驱动未实现ioctl接口}break;}done:mutex_unlock(&rtc->ops_lock); // 确保在退出前释放锁return err;
}
如果是读取时间命令的话,就调用 rtc_read_time 函数获取当前 RTC 时钟, rtc_read_time 函数会调用__rtc_read_time 函数。
err = rtc_read_time(rtc, &tm); // 调用核心时间读取函数
__rtc_read_time 函数
__rtc_read_time 函数内容如下:
static int __rtc_read_time(struct rtc_device *rtc, struct rtc_time *tm)
{int err;/* 驱动基础检查 */if (!rtc->ops) // 检查RTC操作集是否存在err = -ENODEV; // 无设备错误else if (!rtc->ops->read_time) // 检查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;
}
__rtc_read_time 函数,会通过调用 rtc_class_ops 中的read_time 来从 RTC 设备中获取当前时间。
rtc_dev_ioctl 函数对其他的命令处理都是类似的:
- 比如 RTC_ALM_READ 命令会通过 rtc_read_alarm 函数获取到闹钟值,
- 而 rtc_read_alarm 函数,最终会调用 rtc_class_ops 中的 read_alarm 函数来获取闹钟值。
Linux 内核中 RTC 驱动调用流程,如图:
当 rtc_class_ops 准备好以后,需要将其注册到 Linux 内核中,使用rtc_device_register函数完成注册工作。
rtc_device_register函数
rtc_device_register函数,会申请一个rtc_device并且初始化这个rtc_device,最后向调用者返回这个 rtc_device。
rtc_device_register函数原型如下:
struct rtc_device *rtc_device_register(const char *name,struct device *dev,const struct rtc_class_ops *ops,struct module *owner)
- name:设备名字。
- dev: 设备。
- ops: RTC 底层驱动函数集。
- owner:驱动模块拥有者。
- 返回值: 注册成功的话就返回 rtc_device,错误的话会返回一个负值。
rtc_device_unregister 函数
当卸载 RTC 驱动的时候,需要调用 rtc_device_unregister 函数来注销注册的 rtc_device,
rtc_device_unregister 函数原型如下:
void rtc_device_unregister(struct rtc_device *rtc)
- rtc:要删除的 rtc_device。
还有另外一对 rtc_device 注册函数,分别为注册和注销 rtc_device。
- devm_rtc_device_register
- devm_rtc_device_unregister
这四个函数的对比如下:
示例代码如下:
#include <linux/rtc.h>
#include <linux/platform_device.h>/* 驱动私有数据结构 */
struct my_rtc_data {struct rtc_device *rtc;void *regs; // 假设的硬件寄存器基地址
};/* 实现RTC操作函数集 */
static const struct rtc_class_ops my_rtc_ops = {.read_time = my_rtc_read_time,.set_time = my_rtc_set_time,// 其他必要操作...
};/* 标准注册/注销方式示例 */
static int standard_register_example(struct platform_device *pdev)
{struct my_rtc_data *data;struct rtc_device *rtc;data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);if (!data)return -ENOMEM;/* 硬件初始化... */// 标准注册(需手动注销)rtc = rtc_device_register("my_rtc", &pdev->dev, &my_rtc_ops, THIS_MODULE);if (IS_ERR(rtc)) {dev_err(&pdev->dev, "Failed to register RTC\n");return PTR_ERR(rtc);}data->rtc = rtc;platform_set_drvdata(pdev, data);return 0;
}/* 设备托管注册方式示例 */
static int devm_register_example(struct platform_device *pdev)
{struct my_rtc_data *data;struct rtc_device *rtc;data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);if (!data)return -ENOMEM;/* 硬件初始化... */// 设备托管注册(自动注销)rtc = devm_rtc_device_register(&pdev->dev, "my_rtc", &my_rtc_ops, THIS_MODULE);if (IS_ERR(rtc)) {dev_err(&pdev->dev, "Failed to register RTC\n");return PTR_ERR(rtc);}data->rtc = rtc;platform_set_drvdata(pdev, data);return 0;
}/* 驱动移除函数(演示两种注销方式) */
static int my_rtc_remove(struct platform_device *pdev)
{struct my_rtc_data *data = platform_get_drvdata(pdev);/* * 如果使用标准注册方式,需要显式注销:* rtc_device_unregister(data->rtc);* * 如果使用devm注册,则无需任何操作,* 设备框架会自动调用devm_rtc_device_unregister*/return 0;
}/* 平台驱动声明 */
static struct platform_driver my_rtc_driver = {.probe = devm_register_example, // 或 standard_register_example.remove = my_rtc_remove,.driver = {.name = "my-rtc",},
};module_platform_driver(my_rtc_driver);