正点原子嵌入式linux驱动开发——Linux RTC驱动

RTC也就是实时时钟,用于记录当前系统时间,对于Linux系统而言时间是非常重要的,就和使用Windows电脑或手机查看时间一样,在使用Linux设备的时候也需要查看时间。本章就来学习一下如何编写Linux下的RTC驱动程序。

Linux内核RTC驱动简介

RTC设备驱动是一个标准的字符设备驱动,应用程序通过open、release、read、write和ioctl等函数完成对RTC设备的操作,本章主要学习如何使用STM32MP1内部自带的 RTC外设

Linux内核将RTC设备抽象为rtc_device结构体,因此RTC设备驱动就是申请并初始化rtc_device,最后将rtc_device注册到Linux内核里面,这样Linux内核就有一个RTC设备的。至于RTC设备的操作肯定是用一个操作集合(结构体)来表示的,先来看一下rtc_device结构体, 此结构体定义在include/linux/rtc.h文件中,结构体内容如下 (删除条件编译):

rtc_device结构体

需要重点关注的是ops成员变量,这是一个rtc_class_ops类型的指针变量,rtc_class_ops为RTC设备的最底层操作函数集合,包括从RTC设备中读取时间、向RTC设备写入新的时间值等。因此,rtc_class_ops是需要用户根据所使用的RTC设备编写的,此结构体定义在include/linux/rtc.h文件中,内容如下:

rtc_class_ops结构体

看名字就知道rtc_class_ops操作集合中的这些函数是做什么的了,但是要注意,rtc_class_ops中的这些函数只是最底层的RTC设备操作函数,并不是提供给应用层的file_operations函数操作集。RTC是个字符设备,那么肯定有字符设备的file_operations函数操作集,Linux内核提供了一个RTC通用字符设备驱动文件,文件名为drivers/rtc/dev.c dev.c文件提供了所有RTC设备共用的file_operations函数操作集,如下所示:

RTC通用file_operations操作集

示例代码43.1.3,标准的字符设备操作集。应用程序可以通过ioctl函数来设置/读取时间、设置/读取闹钟的操作,对应的rtc_dev_ioctl函数就会执行。rtc_dev_ioctl最终会通过操作rtc_class_ops中的read_time、set_time等函数来对具体RTC设备的读写操作。简单来看一下rtc_dev_ioctl函数,函数内容如下(有省略):

示例代码 43.1.4 rtc_dev_ioctl 函数代码段
202 static long rtc_dev_ioctl (struct file *file,
203                            unsigned int cmd, unsigned long arg)
204 {
205     int err = 0;
206     struct rtc_device *rtc = file->private_data;
207     const struct rtc_class_ops *ops = rtc->ops;
208     struct rtc_time tm;
209     struct rtc_wkalrm alarm;
210     void __user *uarg = (void __user *)arg;
211
212     err = mutex_lock_interruptible(&rtc ops_lock);
213     if (err)
214         return err;
......
253     switch (cmd) {
......
317     case RTC_RD_TIME: /* 读取时间 */
318         mutex_unlock(&rtc->ops_lock);
319
320         err = rtc_read_time(rtc, &tm);
321         if (err < 0)
322             return err
323
324         if (copy_to_user(uarg, &tm, sizeof(tm)))
325             err = -EFAULT;
326         return err;
327
328     case RTC_SET_TIME: /* 设置时间 */
329         mutex_unlock(&rtc->ops_lock);
330
331         if(copy_from_user(&tm, uarg, sizeof(tm)))
332             return -EFAULT;
333
334         return rtc_set_time(rtc, &tm);
......
385     default:
386         /* Finally try the driver's ioctl interface */
387         if (ops->ioctl) {
388             err = ops->ioctl(rtc->dev.parent, cmd, arg);
389             if (err == -ENOIOCTLCMD)
390                 err = -ENOTTY;
391         } else {
392             err = -ENOTTY;
393         }
394         break;
395     }
396
397 done:
398     mutex_unlock(&rtc->ops_lock);
399     return err;
400 }

第317行,RTC_RD_TIME为时间读取命令。

第320行,如果是读取时间命令的话就调用rtc_read_time函数获取当前RTC时钟,rtc_read_time会调用__rtc_read_time函数,__rtc_read_time函数内容如下:

__rtc_read_time函数代码段

从第94行可以看出,__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驱动调用流程就很清晰了,如下图所示:

Linux RTC驱动调用流程

当rtc_class_ops准备好以后需要将其注册到Linux内核中,这里可以使用rtc_device_register函数完成注册工作。此函数会申请一个rtc_device并且初始化这个rtc_device,最后向调用者返回这个rtc_device,此函数原型如下:

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驱动的时候需要调用rtc_device_unregister函数来注销注册的 rtc_device,函数原型如下:

void rtc_device_unregister(struct rtc_device *rtc)

函数参数和返回值含义如下:

  • rtc:要删除的rtc_device。
  • 返回值:无。

还有另外一对rtc_device注册函数devm_rtc_device_register和devm_rtc_device_unregister
分别为注册和注销rtc_device。

STM32MP1内部RTC驱动分析

STM32MP1的RTC驱动不用自己编写,因为ST已经写好了。其实对于大多数的SOC来讲,内部RTC驱动都不需要自己去编写,半导体厂商会编写好。分析驱动,先从设备树入手,打开stm32mp151.dtsi,在里面找到如下rtc设备节点,节点内容如下所示:

stm32mp151.dtsi文件rtc节点

第1747行设置兼容属性compatible的值为“st,stm32mp1-rtc”,因此在Linux内核源码中搜索此字符串即可找到对应的驱动文件,此文件为drivers/rtc/rtc-stm32.c,在rtc-stm32.c文件中找到如下所示内容:

设备platform驱动框架

第719-723行,设备树ID表。第722行,刚好有一个compatible属性和设备树的rtc的
compatible属性值一样,所以rtc设备节点会和此驱动匹配。

第1020-1028行,标准的platform驱动框架,当设备和驱动匹配成功以后stm32_rtc_probe函数就会执行,来看一下stm32_rtc_probe函数,函数内容如下(有省略):

示例代码 43.2.3 stm32_rtc_probe 函数代码段
789 static int stm32_rtc_probe(struct platform_device *pdev)
790 {
791     struct stm32_rtc *rtc;
792     const struct stm32_rtc_registers *regs;
793     struct resource *res;
794     int ret;
795
796     rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
797     if(!rtc)
798         return -ENOMEM;
799
800     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
801     rtc->base = devm_ioremap_resource(&pdev->dev, res);
......
856     ret = clk_prepare_enable(rtc->rtc_ck);
857     if (ret)
858         goto err;
......
872     ret = stm32_rtc_init(pdev, rtc);
873     if(ret)
874         goto err;
875
876     rtc->irq_alarm = platform_get_irq(pdev, 0);
877     if(rtc->irq_alarm <= 0) {
878         ret = rtc->irq_alarm;
879         goto err;
880     }
......
892     rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
893                     &stm32_rtc_ops, THIS_MODULE);
894     if(IS_ERR(rtc->rtc_dev)) {
895         ret = PTR_ERR(rtc->rtc_dev);
896         dev_err(&pdev->dev, "rtc device registration failed, err=%d\n",
897             ret);
898         goto err;
899     }
900
901     /* Handle RTC alarm interrupts */
902     ret = devm_request_threaded_irq(&pdev->dev, rtc->irq_alarm, NULL,
903                     stm32_rtc_alarm_irq, IRQF_ONESHOT,
904                     pdev->name, rtc);
905     if(ret) {
906         dev_err(&pdev->dev, "IRQ%d (alarm interrupt) already
claimed\n",
907             rtc->irq_alarm);
908         goto err;
909     }
......
940
941     return 0;
......
954 }

第796行,调用devm_kzalloc申请rtc大小的空间,返回申请空间的首地址。

第800行,调用platform_get_resource函数从设备树中获取到RTC外设寄存器基地址。

第801行,调用函数devm_ioremap_resource 完成内存映射,得到RTC外设寄存器物理基地址对应的虚拟地址。

第856行,调用clk_prepare_enable函数使能时钟。

第872行,初始化STM32MP1 rtc的寄存器。

第876行,获取设备树的中断号。

第892行,调用devm_rtc_device_register函数向系统注册rtc_devcie,RTC底层驱动集为stm32_rtc_ops。stm32_rtc_ops操作集包含了读取/设置RTC时间,读取/设置闹钟等函数。

第902行,调用devm_request_threaded_irq函数请求RTC中断,中断服务函数为stm32_rtc_alarm_irq,用于RTC闹钟中断。

stm32_rtc_ops内容如下所示:

rtc_class_ops操作集

就以第624行的stm32_rtc_read_time函数为例讲解一下rtc_class_ops的各个RTC底层操作函数该如何去编写。stm32_rtc_read_time函数用于读取RTC时间值,此函数内容如下所示:

stm32_rtc_read_time代码段

第371-372行,调用readl_relaxed读取STM32MP1的RTC_TR和RTC_DR这两个寄存器的值,其中TR寄存器为RTC时间寄存器,保存着时、分、秒信息;DR为RTC的日期寄存器,保存着年、月、日信息。通过这两个寄存器就可以得到RTC时间。

第374-381行,前两行获取到了TR和DR这两个寄存器的值,这里需要从这两个寄存器值中提取出具体的年、月、日和时、分、秒信息。

第385行,上面得到的时间信息为BCD格式的,这里通过bcd2tm函数将BCD格式转换
为rtc_time格式,rtc_time结构体定义如下:

rtc_time结构体

RTC时间查看与设置

使能内部RTC

在Linux内核移植的时候,设备树是经过精简的,没有启动RTC功能。打开stm32mp157d-atk.dts文件,添加如下代码:

示例代码43.3.1.1 rtc节点信息 
1 &rtc { 
2     status = "okay"; 
3 };

追加的RTC节点内容很简单,就是把status属性改为“okay”。接着重新编译设备树然后使用新编译的stm32mp157d-atk.dtb文件启动开发板。

查看时间

RTC是用来记时的,因此最基本的就是查看时间,Linux内核启动的时候可以看到系统时钟设置信息,如下图所示:

Linux启动log信息

从上图中可以看出,Linux内核在启动的时候将rtc设置为rtc0,大家的启动信息可能会和上图中不同,但是基本上都是一样的。

如果要查看时间的话输入“date”命令即可,结果如下图所示:

当前时间值

从上图可以看出,当前时间为2000年1月1日03:30:29,很明显时间不对,需要重新设置RTC时间。

RTC时间设置也是使用的date命令,输入“date --help”命令即可查看date命令如何设置系统时间,结果如下图所示:

date命令帮助信息

比如现在设置当前时间为2021年5月2日18:53:00,因此输入如下命令:

date -s "2021-05-02 18:53:00"

设置完成以后再次使用date命令查看一下当前时间就会发现时间改过来了。

注意使用“date -s”命令仅仅是修改了当前时间,此时间还没有写入到STM32MP1内部RTC里面或其他的RTC芯片里面,因此系统重启以后时间又会丢失。需要将当前的时间写入到RTC里面,这里要用到hwclock命令,输入如下命令将系统时间写入到RTC里面:

hwclock -w //将当前系统时间写入到 RTC里面

时间写入到RTC里面以后就不怕系统重启以后时间丢失了,如果STM32MP1开发板底板接了纽扣电池,那么开发板即使断电了时间也不会丢失。可以尝试一下不断电重启和断电重启这两种情况下开发板时间会不会丢失。

总结

这一章节比较简单,因为Linux内核已经实现了RTC的驱动,对我们来说我只要会用“date”命令和“hwclock”命令去修改使用RTC就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/109637.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法笔记【8】-合并排序算法

文章目录 一、前言二、合并排序算法基本原理三、实现步骤四、优缺点分析 一、前言 合并排序算法通过采用分治策略和递归思想&#xff0c;实现了高效、稳定的排序功能。本文将深入探讨合并排序算法的原理、实现步骤&#xff0c;并讨论其优缺点。 二、合并排序算法基本原理 合…

一文看懂完整的研究生生活规划

很多人在刚从本科步入研究生生活的时候,总是对于自己三年的研究生生活没有清晰的规划,总是在各种浪费时间,没有拿到想要的东西,也没有学到想学的东西,亦或是没有找到理想的工作,最后草草的毕业。这个时候我们就应该对于自己的研究生生活有个清晰的规划,帮助我们不留遗憾…

人大与加拿大女王大学金融管理硕士项目:开启国际视野,成就金融领袖

生活中&#xff0c;我们总会遇到各种各样的困难和挑战。有时候&#xff0c;我们会感到沮丧、迷茫甚至绝望。但是&#xff0c;正是这些困难和挑战&#xff0c;让我们变得更加坚强、勇敢和成熟。在这个职场竞争愈发激烈的时代&#xff0c;不断地充实自己是非常重要的。如果你从事…

IP代理被低估的作用,你知道吗?

IP说简单不简单&#xff0c;说复杂也不复杂&#xff0c;打个比方&#xff0c;IP就好比我们上网的一个门牌号&#xff0c;每家每户都会有一个门牌号&#xff0c;而且是唯一的地址。而代理IP&#xff08;代理服务器&#xff09;是一个位于中间的服务器&#xff0c;充当客户端和目…

centos部署tomcat

Java Downloads | Oracle 上面是下载网址 Tomcat是由Apache开发的一个Servlet容器&#xff0c;实现了对Servlet和JSP的支持&#xff0c;并提供了作为Web服务器的一些特有功能&#xff0c;如Tomcat管理和控制平台&#xff0c;安全域管理和Tomcat阀 简单来说&#xff1a;Tomcat…

【人口数据集总结】WorldPop、GWPv4等

1 全球人口数据WorldPop 数据详解可参见另一博客-【数据集8】全球人口数据WorldPop详解。 WorldPop是由南安普顿大学在2013年10月发起的全球人口数据评估,将AfriPop,AsiaPop和AmeriPop人口调查项目整合到一起。数据集已经被众多的组织和机构使用:联合国开发计划署,联合国…

【多线程相关其二】进程与线程

进程vs线程 进程&#xff08;process&#xff09;指的是正在运行的程序的实例&#xff0c;即an instance of a computer that is being executed。用拆字法理解就是&#xff1a;进行中的程序。程序是一个没有生命的实体&#xff0c;只有处理器执行它的时候才能成为一个活动的实…

技术分享| anyRTC低延时直播优化

直播系统就是把活动现场的音频或视频信号经数字压缩后&#xff0c;传送到直播多媒体服务器(CDN)上&#xff0c;在互联网上供广大网友或授权特定人群收听或收看。而随着技术的日益更新&#xff0c;人民对于直播的互动性&#xff0c;实时性要求更高了&#xff0c;传统的直播少则几…

Spring Boot中配置默认的HikariCP数据源

在了解HiKari之前&#xff0c;我们需要先了解关于数据访问的相关概念&#xff1a; 什么是JDBC JDBC&#xff08;Java Database Connectivity&#xff09;是Java编程语言用于与数据库进行交互的标准API。它提供了一组类和接口&#xff0c;用于执行数据库操作&#xff0c;如连接…

JVM虚拟机:从结构到指令让你对栈有足够的认识

本文重点 在前面的课程中,我们学习了运行时数据区的大概情况,从本文开始,我们将对一些组件进行详细的介绍,本文我们将学习栈。栈内存主管java的运行,是在线程创建时创建的,它是线程私有的,它的生命周期是跟随线程的生命期,也就是说线程结束栈内存就释放了,对于栈来说…

Elasticsearch:标量量化 101 - scalar quantization 101

作者&#xff1a;BENJAMIN TRENT 什么是标量量化以及它是如何工作的&#xff1f; 大多数嵌入模型输出 float32 向量值。 虽然这提供了最高的保真度&#xff0c;但考虑到向量中实际重要的信息&#xff0c;这是浪费的。 在给定的数据集中&#xff0c;嵌入永远不需要每个单独维度…

java项目之驾校预约管理系统(ssm框架)

项目简介 校预约管理系统实现了以下功能&#xff1a; 管理员&#xff1a;首页、个人中心、学员管理、驾校教练管理、驾校车辆管理、预约管理、取消预约管理、驾校公告管理、系统管理。驾校教练&#xff1a;首页、个人中心、驾校教练管理、预约管理、取消预约管理。学员&#…

编译环境里存在yaml-cpp的多个版本时可能引起的问题

有时要编译的程序自带了特定版本的yaml-cpp&#xff0c;同时系统目录下也安装了更高版本的yaml-cpp&#xff0c;这时可能引起编译错误&#xff0c;就是某些yaml-cpp的API不认识&#xff0c;例如&#xff1a; 出现这种问题倒好办&#xff0c;正常情况下不可能&#xff0c;肯定能…

【Unity PlasticSCM】记录:从介绍 下载 到拉取项目

实习的时候项目是svn管理的&#xff0c;这次mini的项目管理最后选择了美术策划友好的plasticSCM&#xff0c;但之前没有接触过&#xff0c;所以决定花费一点时间去了解&#xff0c;然后记录一下中间遇到的一些问题。 了解及下载Plastic b站很详细介绍PlasticSCM&#xff1a;Un…

电力巡检/电力抢修行业解决方案:AI+视频技术助力解决巡检监管难题

一、行业背景 随着国民经济的蓬勃发展&#xff0c;工业用电和居民用电需求迅速增加&#xff0c;电厂、变电站、输电线路高负荷运转&#xff0c;一旦某个节点发生故障&#xff0c;对生产、生活造成巨大的影响。目前电力行业生产现场人员、设备较多&#xff0c;而生产监督员有限…

JMeter简单使用

JMeter是一个功能强大的开源性能测试工具&#xff0c;用于对各种应用程序、协议和服务器进行性能和负载测试。它被广泛用于测试Web应用程序的性能&#xff0c;并可以模拟多种负载条件和行为。 JMeter使用 添加线程组 设置线程组的配置 设置请求 配置请求 添加监听器 查看压…

【网络安全】Seeker内网穿透追踪定位

Seeker追踪定位对方精确位置 前言一、kali安装二、seeker定位1、ngrok平台注册2、获取一次性邮箱地址3、ngrok平台登录4、ngrok下载5、ngrok令牌授权6、seeker下载7、运行seeker定位8、运行隧道开启监听9、伪装链接10、用户点击&#xff08;获取定位成功&#xff09;11、利用经…

2023年N1叉车司机证模拟考试题库及N1叉车司机理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年N1叉车司机证模拟考试题库及N1叉车司机理论考试试题是由安全生产模拟考试一点通提供&#xff0c;N1叉车司机证模拟考试题库是根据N1叉车司机最新版教材&#xff0c;N1叉车司机大纲整理而成&#xff08;含2023年…

蓝凌EIS智慧协同平台saveImg接口存在任意文件上传漏洞

蓝凌EIS智慧协同平台saveImg接口存在任意文件上传漏洞 一、蓝凌EIS简介二、漏洞描述三、影响版本四、fofa查询语句五、漏洞复现六、深度复现1、发送如花2、哥斯拉直连 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者…

一文教你解决git请求github时候超时的问题

前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一文教你解决git请求github的时候超时问题 一. 问题二. 当前 ssh 实现原理三. 创建ssh key3.1 将ssh key加入github配置中3.2 测试连…