Linux 驱动笔记
目录
链接
1. Linux 驱动框架
2. Linux 驱动加载逻辑
3. 字符设备基础
4. 并发与竞争
5. 高级字符设备进阶
6. 中断
7. 平台总线
8. 设备树
9. 设备模型
10. 热插拔
11. pinctrl 子系统
12. gpio 子系统
13. 输入子系统
14. 单总线
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. 网络设备
23. ADC
24. IIO
25. USB
26. LCD
RTC 基础
RTC 介绍
RTC(Real-Time Clock )即实时时钟 ,是一种集成电路或模块,用于在电子系统中提供精确的时间信息 。
与系统主处理器(CPU)不同,RTC 主要负责维护系统的实时日期和时间 ,即使在设备关闭或断电的情况下,RTC 也能持续运行并保持时间准确性。
RTC 的主要功能 :
提供实时时间 :RTC 能够持续跟踪当前的日期和时间,通常包括年、月、日、时、分、秒等信息。
断电保持时间 :RTC 通常配备有备用电池(如纽扣电池),即使主系统断电,RTC也能继续运行,确保时间信息不会丢失。
RTC 的应用场景 :
电子时钟:如电脑、手机、智能手表等设备中的时钟功能。
嵌入式系统:在工业控制、物联网设备中,RTC 用于记录事件发生的时间。
数据记录:在需要时间戳的场景中,RTC 提供准确的时间信息。
定时任务:用于定时唤醒设备或执行特定操作。
RTC 的特点 :
低功耗 :RTC 通常设计为低功耗运行,适合长时间使用。
高精度 :能够提供较为精确的时间信息,误差较小。
独立性 :即使主系统关闭,RTC 也能独立运行。
内部 RTC 和外部 RTC
实时时钟有着外部 RTC 和内部 RTC 这两种常见的实现方案。
外部 RTC 是独立于主控芯片的专用 RTC 芯片,通过 I2C、SPI 等通信接口与主控芯片连接 。
优点
具备高精度的特点,能够提供更准确的时间记录。
外部 RTC 拥有独立的电源管理电路,可配备纽扣电池实现长时间独立运行,即使主控芯片断电或损坏,也能确保时间记录的连续性。
缺点
外部 RTC 的成本较高,需要额外采购芯片及相关元件,同时增加了电路设计的复杂性和开发难度,并占用主控芯片的通信资源。
因此,外部 RTC 更适合对时间精度、可靠性和功能有较高要求的场景。在 iTOP-RK3568 开发板上就集成了外部 RTC,所使用的芯片是 RX8010,如下图所示:
内部 RTC 是集成在主控芯片内部的实时时钟模块
优点
具有硬件集成的特点,无需额外硬件组件,因此设计简单、成本低且易于使用。
缺点
内部 RTC 依赖主控芯片的供电,通常需要备用电池来维持断电后的时间记录
内部 RTC 非常容易受到温度和电压波动的影响,导致时间漂移较大,可靠性也相对较低 。
因此,内部 RTC 更适合对时间精度要求不高、成本敏感的场景,例如家用电器、简单的物联网设备或实时性要求较低的系统。
而 iTOP-RK3568 核心板的电源管理芯片 RK809 默认是集成了内部 RTC 的,但由于上述内部 RTC 的种种缺点,且底板上已经有了外部 RTC,所以内部 RTC 并没有被使能,RK809 电源管理芯片的内部 RTC 电路如下所示:
总结
类别
内部RTC
外部RTC
定义
集成在主控芯片内的RTC模块
独立的专用RTC芯片,通过I²C/SPI等接口连接
特点
- 硬件集成 - 低功耗模式支持 - 依赖主控电源 - 精度有限
- 独立运行 - 高精度 - 多功能扩展
优点
1. 成本低 2. 设计简单 3. 易于使用
1. 高精度 2. 独立性强 3. 功能丰富 4. 可靠性高
缺点
1. 精度不足 2. 可靠性较低 3. 缺乏独立性
1. 成本较高 2. 设计复杂 3. 占用资源
适用场景
对时间精度要求不高、成本敏感、实时性要求低的系统
对时间精度要求高、需长时间独立运行、需额外功能的场景
RK3568 RTC 外设
可以将这个电路分为 RTC 电源供电部分以及 RTC 芯片电路部分,根据 RTC 芯片电路部分可以得到 RX8010 挂载到了 I2C5 上
该系统的主要功能是通过主电源供电模式和备用电池供电模式的切换 ,确保 RTC 模块在主电源(VCC3V3_SYS)断电时仍能通过备用电池(CR1220)继续工作,从而保持时间信息不丢失。分为以下两种情况。
在系统正常运行时,VCC3V3_SYS 提供 3.3V 电源,二极管 D2 正向导通为 RTC 模块供电,同时二极管 D3 反向截止以防止主电源向电池充电或消耗电池电量;
当系统断电(VCC3V3_SYS 失效)时,二极管 D2 反向截止以防止电池电量通过主电源回路消耗,同时二极管 D3 正向导通,由CR1220 电池为 RTC 模块供电,确保 RTC 继续运行。
RX8010 驱动分析和移植
RTC子系统框架
在上图中 RTC 子系统被划分为了三个层次,分别为用户空间、设备驱动层和硬件层,设备驱动层又包括 RTC 设备驱动和 PWM 核心层:
用户空间是应用程序运行的层级,在这一层,应用程序通过不同的接口与系统进行交互,比如访问 /dev/xxx 设备节点来读写 RTC 时间,或通过 sysfs 和 proc 文件系统来获取或设置硬件状态。
中间的设备驱动层又分为了 RTC 设备驱动和 RTC 核心层,,。
RTC 设备驱动负责操作硬件的具体细节通过与硬件(RTC 芯片)进行直接通信来实现硬件控制,设备驱动程序还负责将硬件设备暴露给上层系统,如 device(设备文件)和 driver(驱动程序)。
/dev/xxx 是指用于操作设备的接口文件,用户应用程序通过它与 RTC 硬件进行交互。
driver 部分则是系统内核的一个模块,负责具体的硬件控制操作。
RTC 核心层负责管理和协调 RTC 的时间管理功能。它确保系统正确地读取和设置时间,同时确保时钟在断电时继续运行。
在 RTC 硬件层代表实际的 RTC 硬件,通过硬件接口(如 I2C、SPI 等)与设备驱动层进行通信,提供实际的时间计时功能。
RTC 驱动源码分析
设备树
1 2 3 4 5 6 7 8 9 10 11 12 &i2c5 { status = "okay" ; rx8010: rx8010@32 { compatible = "epson,rx8010" ; reg = <0x32 > ; status = "okay" ; #clock-cells = <0>; }; };
通过再通过 compatible 属性epson,rx8010找到匹配的驱动文件,驱动的具体路径为 drivers/rtc/rtc-rx8010.c,先来找到驱动的入口函数:
1 2 3 4 5 6 7 8 9 10 static struct i2c_driver rx8010_driver = { .driver = { .name = "rtc-rx8010" , .of_match_table = of_match_ptr(rx8010_of_match), }, .probe = rx8010_probe, .id_table = rx8010_id, }; module_i2c_driver(rx8010_driver);
module_i2c_driver()
这个宏是用来简化 I2C 设备驱动的注册和注销过程的,定义在 include/linux/i2c.h 文件中,具体如下所示
1 2 3 4 5 6 7 8 9 10 11 12 #define module_i2c_driver(__i2c_driver) \ module_driver(__i2c_driver, i2c_add_driver, \ i2c_del_driver)
module_driver()
module_driver 宏也是一个用来简化的宏,对应的宏定义如下所示
include/linux/device/driver.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);
最后来对上述宏进行代入,展开可以得到如下内容:
1 2 3 4 5 6 7 8 9 10 11 static int __init rx8010_driver_init (void ) { return i2c_add_driver(&rx8010_driver); } module_init(rx8010_driver_init); static void __exit rx8010_driver_exit (void ) { i2c_del_driver(&rx8010_driver); } module_exit(rx8010_driver_exit);
rx8010_probe()
当 compatible 匹配之后会进入到 probe 函数,probe 函数内容如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 static int rx8010_probe (struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); const struct rtc_class_ops *rtc_ops ; struct rx8010_data *rx8010 ; int err = 0 ; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK)) { dev_err(&adapter->dev, "doesn't support required functionality\n" ); return -EIO; } rx8010 = devm_kzalloc(&client->dev, sizeof (struct rx8010_data), GFP_KERNEL); if (!rx8010) return -ENOMEM; rx8010->client = client; i2c_set_clientdata(client, rx8010); err = rx8010_init_client(client); if (err) return err; if (client->irq > 0 ) { dev_info(&client->dev, "IRQ %d supplied\n" , client->irq); err = devm_request_threaded_irq(&client->dev, client->irq, NULL , rx8010_irq_1_handler, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "rx8010" , client); if (err) { dev_err(&client->dev, "unable to request IRQ\n" ); return err; } rtc_ops = &rx8010_rtc_ops_alarm; } else { rtc_ops = &rx8010_rtc_ops_default; } rx8010->rtc = devm_rtc_device_register(&client->dev, client->name, rtc_ops, THIS_MODULE); if (IS_ERR(rx8010->rtc)) { dev_err(&client->dev, "unable to register the class device\n" ); return PTR_ERR(rx8010->rtc); } rx8010->rtc->max_user_freq = 1 ; return err; }
probe函数中定义了一个用于存储设备私有数据的 rx8010_data 结构体变量 rx8010,该结构体定义如下所示:
struct rx8010_data
1 2 3 4 5 struct rx8010_data { struct i2c_client *client ; struct rtc_device *rtc ; u8 ctrlreg; };
i2c_client 是指向 I2C 客户端的指针,用于与 RX8010 设备通信,而 rtc_device 结构体表示与 RX8010 设备关联的 RTC(实时时钟)设备,该结构体的定义如下所示:
struct rtc_device
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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 ; 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; long set_offset_nsec; bool registered; bool nvram_old_abi; struct bin_attribute *nvram ; time64_t range_min; timeu64_t range_max; time64_t start_secs; time64_t offset_secs; bool set_start_time; #ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL struct work_struct uie_task ; struct timer_list uie_timer ; 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 };
该结构体是 Linux 内核中 RTC 设备的核心数据结构,涵盖了 RTC 设备的所有功能和状态。
rx8010_init_client()
probe 函数中通过 rx8010_init_client 函数对 RX8010 芯片进行初始化和配置,由于不同的 RTC 芯片寄存器并不相同,因此初始化代码也不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 static int rx8010_init_client (struct i2c_client *client) { struct rx8010_data *rx8010 = i2c_get_clientdata(client); u8 ctrl[2 ]; int need_clear = 0 , err = 0 ; u8 flag; flag = i2c_smbus_read_byte_data(client, RX8010_FLAG); if (err < 0 ) return err; flag &= ~(RX8010_FLAG_VLF); err = i2c_smbus_write_byte_data(client, RX8010_FLAG, flag); err = i2c_smbus_write_byte_data(client, RX8010_RESV17, 0xD8 ); if (err < 0 ) return err; err = i2c_smbus_write_byte_data(client, RX8010_RESV30, 0x00 ); if (err < 0 ) return err; err = i2c_smbus_write_byte_data(client, RX8010_RESV31, 0x08 ); if (err < 0 ) return err; err = i2c_smbus_write_byte_data(client, RX8010_IRQ, 0x00 ); if (err < 0 ) return err; err = i2c_smbus_read_i2c_block_data(rx8010->client, RX8010_FLAG, 2 , ctrl); if (err != 2 ) return err < 0 ? err : -EIO; if (ctrl[0 ] & RX8010_FLAG_VLF) dev_warn(&client->dev, "Frequency stop was detected\n" ); if (ctrl[0 ] & RX8010_FLAG_AF) { dev_warn(&client->dev, "Alarm was detected\n" ); need_clear = 1 ; } if (ctrl[0 ] & RX8010_FLAG_TF) need_clear = 1 ; if (ctrl[0 ] & RX8010_FLAG_UF) need_clear = 1 ; if (need_clear) { ctrl[0 ] &= ~(RX8010_FLAG_AF | RX8010_FLAG_TF | RX8010_FLAG_UF); err = i2c_smbus_write_byte_data(client, RX8010_FLAG, ctrl[0 ]); if (err < 0 ) return err; } rx8010->ctrlreg = (ctrl[1 ] & ~RX8010_CTRL_TEST); return 0 ; }
struct rtc_class_ops
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 if (client->irq > 0 ) { dev_info(&client->dev, "IRQ %d supplied\n" , client->irq); err = devm_request_threaded_irq(&client->dev, client->irq, NULL , rx8010_irq_1_handler, IRQF_TRIGGER_LOW | IRQF_ONESHOT, "rx8010" , client); if (err) { dev_err(&client->dev, "unable to request IRQ\n" ); return err; } rtc_ops = &rx8010_rtc_ops_alarm; } else { rtc_ops = &rx8010_rtc_ops_default; }
probe 函数中会根据设备是否提供了中断号来决定 RTC 芯片的操作模式,但无论是硬件连接还是设备树配置中没有使用中断,所以进入的是第二个判断,也就是rtc_ops = &rx8010_rtc_ops_default;
1 2 3 4 5 static const struct rtc_class_ops rx8010_rtc_ops_default = { .read_time = rx8010_get_time, .set_time = rx8010_set_time, .ioctl = rx8010_ioctl, };
rx8010_rtc_ops_default 为 RX8010 实时时钟芯片提供一组默认的操作函数接口,包括读取当前时间的函数指针、设置当前时间的函数指针以及 ioctl 外控制功能
devm_rtc_device_register()
在 probe 函数中,使用 devm_rtc_device_register 函数注册 rtc 设备,该函数在 drivers/rtc/class.c 文件中,函数具体内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct rtc_device *devm_rtc_device_register (struct device *dev, const char *name, const struct rtc_class_ops *ops, struct module *owner) { struct rtc_device *rtc ; int err; rtc = devm_rtc_allocate_device(dev); if (IS_ERR(rtc)) return rtc; rtc->ops = ops; err = __rtc_register_device(owner, rtc); if (err) return ERR_PTR(err); return rtc; } EXPORT_SYMBOL_GPL(devm_rtc_device_register);
__rtc_register_device()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int __rtc_register_device(struct module *owner, struct rtc_device *rtc){ struct rtc_wkalrm alrm ; int err; if (!rtc->ops) { dev_dbg(&rtc->dev, "no ops set\n" ); return -EINVAL; } rtc->owner = owner; rtc_device_get_offset(rtc); 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.parent, "failed to add char device %d:%d\n" , MAJOR(rtc->dev.devt), rtc->id); else dev_dbg(rtc->dev.parent, "char device (%d:%d)\n" , MAJOR(rtc->dev.devt), rtc->id); rtc_proc_add_device(rtc); rtc->registered = true ; dev_info(rtc->dev.parent, "registered as %s\n" , dev_name(&rtc->dev)); #ifdef CONFIG_RTC_HCTOSYS_DEVICE if (!strcmp (dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE)) rtc_hctosys(rtc); #endif return 0 ; } EXPORT_SYMBOL_GPL(__rtc_register_device);
该函数主要作用是注册一个 RTC 设备到 Linux 内核的 RTC 子系统中 。它完成了一系列初始化和资源分配工作,使得内核可以通过标准接口与该 RTC 设备交互。
重点是调用了cdev_device_add 函数,将 RTC 设备的字符设备和设备结构体注册到内核中,使RTC 设备正式成为内核的一部分,可供用户空间或其他内核模块使用。
然后是其中的 rtc_proc_add_device,将 RTC 设备的信息添加到 proc 子系统当中,便于调试和监控。
移植 RX8010 驱动
可以确定 RX8010 挂载到了 I2C5 上,所以需要在设备树的 i2c5 节点下添加 rx8010 的节点,添加内容如下所示:
1 2 3 4 5 6 7 8 9 &i2c5 { status = "okay" ; rx8010: rx8010@32 { compatible = "epson,rx8010" ; reg = <0x32 >; status = "okay" ; #clock-cells = <0> ; }; };
reg 属性表示 RX8010 的地址为 0x32,这里的地址可以根据 RX8010 数据手册得到,具体如下所示:
这里总共有 8 位的数据,这是因为在实际的数据传输过程中主设备发送的第一个字节包含了从设备地址和读/写位 ,其中从设备地址占字节的高 7 位(bit 7 到 bit 1),所以从上图可以得到 RX8010 的地址为 0110010,换算就可以得到 0x32 了。
然后来到 Linux 内核源码的根目录,使用menuconfig打开RX8010驱动
1 2 3 Device Drivers Real Time Clock <*> Epson RX8010SJ
除此之外还要确保 RK 电源管理芯片的内部 RTC 没有被勾选,确保系统里面不会存在两个RTC 设备
1 2 3 Device Drivers Real Time Clock <*> Rockchip RK805/RK808/ RK809/RK816/ RK817/RK818 RTC
时间相关命令
date 命令
date 是 Linux 系统中一个非常强大的命令行工具,用于显示或设置系统的日期和时间。它不仅可以查看当前的系统时间,还可以设置系统时间、格式化日期和时间的输出、解析时间字符串并进行时间计算。
直接运行 date 命令会显示当前系统的日期和时间,格式为默认的本地时间格式,如下所示
1 2 $ date Mon Jan 5 09:37:49 PM CST 2026
CST表示时区(China Standard Time)
使用 date 命令可以设置系统时间的命令格式如下所示:
1 2 3 4 5 6 7 8 date [MMDDhhmm[[CC]YY][.ss]]
例如将系统时间设置为 2025 年 1 月 20 日 14:23:45:
date 命令支持通过格式化字符串自定义输出的日期和时间格式。在 date 命令后加上 + 和格式化字符串,可以指定输出格式。
格式符
描述
示例
%Y
年份(四位数字)
2023
%m
月份(两位数字)
10
%d
日期(两位数字)
30
%H
小时(24小时制,两位数字)
14
%I
小时(12小时制,两位数字)
02
%M
分钟(两位数字)
23
%S
秒数(两位数字)
45
%A
星期几(完整名称)
Monday
%a
星期几(缩写名称)
Mon
%B
月份(完整名称)
October
%b
月份(缩写名称)
Oct
%p
AM/PM
PM
%Z
时区
CST
例如,将格式为 YYYY-MM-DD HH:MM:SS,并输出当前时间:
1 date "+%Y-%m-%d %H:%M:%S"
hwclock 命令
hwclock 是 Linux 系统中用于管理硬件时钟 的命令行工具。包括查看、设置和同步硬件时钟与系统时钟。
直接运行 hwclock 命令会显示当前硬件时钟的时间。
1 2 $ sudo hwclock 2026-01-05 21:52:44.991760+08:00
hwclock 命令显示的是硬件时钟的时间。硬件时钟通常默认存储的是 UTC 时间,UTC 是目前全球使用的标准时间,它是基于原子时钟的精确时间定义,并且不受地球自转变化的影响。
UTC 是全球时间的基准,不属于任何时区,因此它也被称为世界标准时间。
而 date 命令显示的是系统时钟的时间,系统时钟会根据操作系统的时区设置来调整时间。
上面打印的时区为 CST,即中国时区,那么 date 命令显示的时间会是 UTC+8。因此,系统时钟和硬件时钟使用不同的时间标准,导致二者之间的时间差。假设你在中国(UTC+8),那么两者的时间差正好是 8 小时。
同步硬件时钟与系统时钟
hwclock -s将系统时钟同步到硬件时钟
hwclock -w将硬件时钟同步到系统时钟
每次系统上电启动之后,都会将 RTC 里面的硬件时间同步到系统时间,具体的实现在内核drivers/rtc/class.c 文件的 rtc_hctosys 函数实现的。该函数的具体内容如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #ifdef CONFIG_RTC_HCTOSYS_DEVICE int rtc_hctosys_ret = -ENODEV;static void rtc_hctosys (struct rtc_device *rtc) { int err; struct rtc_time tm ; struct timespec64 tv64 = { .tv_nsec = NSEC_PER_SEC >> 1 , }; err = rtc_read_time(rtc, &tm); if (err) { dev_err(rtc->dev.parent, "hctosys: unable to read the hardware clock\n" ); goto err_read; } tv64.tv_sec = rtc_tm_to_time64(&tm); #if BITS_PER_LONG == 32 if (tv64.tv_sec > INT_MAX) { err = -ERANGE; goto err_read; } #endif err = do_settimeofday64(&tv64); dev_info(rtc->dev.parent, "setting system clock to %ptR UTC (%lld)\n" , &tm, (long long )tv64.tv_sec); err_read: rtc_hctosys_ret = err; } #endif
通过 rtc_read_time 函数读取 RTC 硬件里面存储的时间,然后通过do_settimeofday64 函数将转换之后的时间戳设置为系统时钟。
RTC 应用程序编写
ioctl 宏定义解析
RTC_RD_TIME
1 #define RTC_RD_TIME _IOR('p' , 0x09, struct rtc_time)
_IOR(type, nr, data_type):表示 从内核读取数据到用户空间 。
'p':魔数(magic number),用于区分不同设备的 ioctl 命令(RTC 固定为 'p')。
0x09:命令编号。
struct rtc_time:期望的数据类型。
功能:读取 RTC 硬件当前时间 (注意:不是系统时间!)
RTC_SET_TIME
1 #define RTC_SET_TIME _IOW('p' , 0x0a, struct rtc_time)
_IOW(type, nr, data_type):表示 从用户空间写入数据到内核 。
其他参数同上。
功能:将用户指定的时间写入 RTC 硬件 (通常需要 root 权限)。
struct rtc_time 结构体
这是 Linux 内核定义的标准时间结构(位于 <linux/rtc.h>),与 C 标准库的 struct tm 几乎相同 ,但不保证完全兼容 (尤其在跨平台时)。
字段
范围
说明
⚠️ 注意
tm_sec
0–59
秒
—
tm_min
0–59
分钟
—
tm_hour
0–23
小时(24小时制)
—
tm_mday
1–31
日期(1号起)
不是 0-based!
tm_mon
0–11
月份(0=Jan)
容易出错!
tm_year
年份 - 1900
如 2023 → 123
必须减 1900!
tm_wday
0–6
星期(0=Sun)
驱动通常自动计算
tm_yday
0–365
年内第几天
驱动通常自动计算
tm_isdst
通常为 0
夏令时标志
RTC 硬件一般不支持
关键陷阱 :
设置 2023 年 2 月 15 日 → .tm_year = 123, .tm_mon = 1
打印时要加回去:tm_year + 1900, tm_mon + 1
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/rtc.h> #include <errno.h> #include <string.h> #include <stdlib.h> int main () { int fd; struct rtc_time tm ; fd = open("/dev/rtc0" , O_RDWR); if (fd < 0 ) { perror("Failed to open /dev/rtc0 (try running as root)" ); return EXIT_FAILURE; } if (ioctl(fd, RTC_RD_TIME, &tm) < 0 ) { perror("RTC_RD_TIME" ); close(fd); return EXIT_FAILURE; } printf ("Current RTC time: %d-%02d-%02d %02d:%02d:%02d\n" , tm.tm_year + 1900 , tm.tm_mon + 1 , tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); tm.tm_year = 123 ; tm.tm_mon = 5 ; tm.tm_mday = 15 ; tm.tm_hour = 12 ; tm.tm_min = 0 ; tm.tm_sec = 0 ; tm.tm_wday = tm.tm_yday = tm.tm_isdst = 0 ; if (ioctl(fd, RTC_SET_TIME, &tm) < 0 ) { if (errno == EPERM || errno == EACCES) { fprintf (stderr , "Error: Permission denied. Run as root or use 'sudo'.\n" ); } else { perror("RTC_SET_TIME" ); } close(fd); return EXIT_FAILURE; } if (ioctl(fd, RTC_RD_TIME, &tm) < 0 ) { perror("RTC_RD_TIME (after set)" ); close(fd); return EXIT_FAILURE; } printf ("Modified RTC time: %d-%02d-%02d %02d:%02d:%02d\n" , tm.tm_year + 1900 , tm.tm_mon + 1 , tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); close(fd); return EXIT_SUCCESS; }