当前位置: 首页 > news >正文

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);
http://www.dtcms.com/a/349930.html

相关文章:

  • 一道MySQL笔试题: 输出 100 以内质数
  • VIVO/OPPO手机,显示5G开关
  • 【SystemUI】锁屏来通知默认亮屏Wake模式
  • Mac 菜单栏多合一工具自荐:FancyTool
  • LeetCode算法日记 - Day 22: 提莫攻击、Z字形变换
  • 电影感人文街拍摆摊纪实摄影后期Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • 从手术室到街头摄像头:多模态融合如何让AI“看得懂”万物?
  • 搭建ftp服务器(主动模式,被动模式)
  • Canvas 动态高度文本图片生成器
  • Linux 详谈Ext系列⽂件系统(一)
  • 嵌入式(ARM方向)面试常见问题及解答
  • 【ARM】MDK在debug模式下断点的类型
  • blazor 学习笔记--vscode debug
  • C++11(Linux/GCC)字节序工具
  • 2025年09月计算机二级Python选择题每日一练——第七期
  • 栈指针(Stack Pointer)是什么?
  • 设置密钥连接服务器
  • 【基础-单选】向服务器提交表单数据,以下哪种请求方式比较合适
  • Linux 离线安装lrzsz(rz、sz上传下载小插件)
  • 什么是高防服务器?如何进行防御?
  • UE5多人MOBA+GAS 54、用户登录和会话创建请求
  • 矩阵系统源代码开发,支持OEM贴牌
  • 深入解析ffmpeg.dll:电脑中的关键组件及其相关问题解决​
  • 【龙泽科技】汽车车身测量与校正仿真教学软件【赛欧+SHARK】
  • 8851定期复盘代码实现设计模式的于芬应用
  • 中国计算机学会(CCF)推荐学术会议-B(计算机图形学与多媒体):DCC 2026
  • 《信息检索与论文写作》实验报告一 EI数据库检索
  • Allegro约束管理器设置详细教程
  • JUC之volatile关键字
  • 高通平台wifi--p2p issue