RTC驱动分析
linux中的rtc驱动位于drivers/rtc
下,里面包含了许多开发平台的RTC驱动,我们这里是以S3C24xx为主,所以它的RTC驱动为rtc-s3c.c
1、入口函数s3c_rtc_init分析
进入./drivers/rtc/rtc-s3c.c,找到入口函数,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static struct platform_driver s3c2410_rtcdrv = { .probe = s3c_rtc_probe, .remove = s3c_rtc_remove, .suspend = s3c_rtc_suspend, .resume = s3c_rtc_resume, .driver = { .name = "s3c2410-rtc" , .owner = THIS_MODULE, }, }; static char __initdata banner[] = "S3C24XX RTC, (c) 2004,2006 Simtec Electronics\n" ;static int __init s3c_rtc_init (void ) { printk(banner); return platform_driver_register(&s3c2410_rtcdrv); } module_init(s3c_rtc_init);
这里注册了一个“s3c2410-rtc”名称的平台设备驱动
而“s3c2410-rtc”的平台设备,在./arch/arm/plat-s3c24xx/devs.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 static struct resource s3c_rtc_resource [] = { [0 ] = { .start = S3C24XX_PA_RTC, .end = S3C24XX_PA_RTC + 0xff , .flags = IORESOURCE_MEM, }, [1 ] = { .start = IRQ_RTC, .end = IRQ_RTC, .flags = IORESOURCE_IRQ, }, [2 ] = { .start = IRQ_TICK, .end = IRQ_TICK, .flags = IORESOURCE_IRQ } }; struct platform_device s3c_device_rtc = { .name = "s3c2410-rtc" , .id = -1 , .num_resources = ARRAY_SIZE(s3c_rtc_resource), .resource = s3c_rtc_resource, }; EXPORT_SYMBOL(s3c_device_rtc);
当内核匹配到有与它名称同名的平台设备,就会调用**.probe函数**,接下来我们便进入s3c2410_rtcdrv->probe函数(即s3c_rtc_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 static int s3c_rtc_probe (struct platform_device *pdev) { struct rtc_device *rtc ; struct resource *res ; int ret; s3c_rtc_tickno = platform_get_irq(pdev, 1 ); s3c_rtc_alarmno = platform_get_irq(pdev, 0 ); res = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1 ,pdev->name); s3c_rtc_base = ioremap(res->start, res->end - res->start + 1 ); s3c_rtc_enable(pdev, 1 ); s3c_rtc_setfreq(s3c_rtc_freq); rtc = rtc_device_register("s3c" , &pdev->dev, &s3c_rtcops,THIS_MODULE); rtc->max_user_freq = 128 ; platform_set_drvdata(pdev, rtc); return 0 ; }
显然最终会调用rtc_device_register()函数来向内核注册rtc_device设备,注册成功会返回一个已注册好的rtc_device,而s3c_rtcops是一个 rtc_class_ops结构体 ,里面就是保存如何操作这个rtc设备的函数,比如读写RTC时间,读写闹钟时间等,注册后,会保存在rtc_device->ops里。
rtc_device_register()函数在drivers/rtc/Class.c
文件内被定义。Class.c文件主要定义了RTC子系统,而内核初始化,便会进入Class.c。
再从Class.c中的初始化函数看:
先进入rtc_init()在创建了相关的类之后会调用rtc_dev_init(),在rtc_dev_init()中会通过**alloc_chrdev_region()**函数来注册字符设备:
1 err = alloc_chrdev_region(&rtc_devt, 0 , RTC_DEV_MAX, "rtc" );
2、rtc_device_register()函数分析
Class.c
中的**alloc_chrdev_region()函数和./arch/arm/plat-s3c24xx/devs.c
中通过 rtc_device_register()**函数注册RTC设备,会有什么关系?
接下来便来看rtc_device_register(),代码如下:
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 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 ; ... ... rtc = kzalloc(sizeof (struct rtc_device), GFP_KERNEL); rtc->id = id; rtc->ops = ops; rtc->owner = owner; rtc->max_user_freq = 64 ; rtc->dev.parent = dev; rtc->dev.class = rtc_class; rtc->dev.release = rtc_device_release; ... ... rtc_dev_prepare(rtc); ... ... rtc_dev_add_device(rtc); rtc_sysfs_add_device(rtc); rtc_proc_add_device(rtc); ... ... return rtc; }
上面的rtc_dev_prepare(rtc)和rtc_dev_add_device(rtc)主要做了以下两件事(位于./drivers/rtc/rtc-dev.c
):
显然这里就是利用新方法注册字符设备
.probe函数总结
所以“s3c2410-rtc”平台设备驱动的.probe主要做了以下几件事:
3、file_operations结构体分析
综上所述,rtc->char_dev字符设备中绑定的file_operations结构体rtc_dev_fops 为:
1 2 3 4 5 6 7 8 9 10 static const struct file_operations rtc_dev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = rtc_dev_read, .poll = rtc_dev_poll, .ioctl = rtc_dev_ioctl, .open = rtc_dev_open, .release = rtc_dev_release, .fasync = rtc_dev_fasync, };
3.1、open函数
当应用层open(”/dev/rtcXX”)时,就会调用rtc_dev_fops->rtc_dev_open() ,我们来看看如何open的:
1 2 3 4 5 6 7 8 9 10 11 12 static int rtc_dev_open (struct inode *inode, struct file *file) { struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev); const struct rtc_class_ops *ops = rtc->ops; file->private_data = rtc; err = ops->open ? ops->open(rtc->dev.parent) : 0 ; mutex_unlock(&rtc->char_lock); return err; }
container_of:
1 2 3 4 5 6 #define container_of(ptr, type, member) ({ \ const typeof ( ((type *)0 )->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
通过一个结构变量中一个成员的地址member找到这个结构体变量的首地址ptr。
显然最终还是调用rtc_device下的s3c_rtcops ->open函数:
1 2 3 4 5 6 7 8 9 10 static const struct rtc_class_ops s3c_rtcops = { .open = s3c_rtc_open, .release = s3c_rtc_release, .ioctl = s3c_rtc_ioctl, .read_time = s3c_rtc_gettime, .set_time = s3c_rtc_settime, .read_alarm = s3c_rtc_getalarm, .set_alarm = s3c_rtc_setalarm, .proc = s3c_rtc_proc, };
即**s3c_rtc_open()**函数,而s3c_rtc_open()函数里主要是申请了两个中断,一个闹钟中断,一个计时中断:
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 static int s3c_rtc_open (struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct rtc_device *rtc_dev = platform_get_drvdata(pdev); int ret; ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,IRQF_DISABLED, "s3c2410-rtc alarm" , rtc_dev); if (ret) { dev_err(dev, "IRQ%d error %d\n" , s3c_rtc_alarmno, ret); return ret; } ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,IRQF_DISABLED, "s3c2410-rtc tick" , rtc_dev); if (ret) { dev_err(dev, "IRQ%d error %d\n" , s3c_rtc_tickno, ret); goto tick_err; } return ret; tick_err: free_irq(s3c_rtc_alarmno, rtc_dev); return ret; }
3.2、ioctl函数
当我们应用层open后,使用 ioctl(int fd, unsigned long cmd, …)时,就会调用rtc_dev_fops-> rtc_dev_ioctl ():
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 static int rtc_dev_ioctl (struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg) { struct rtc_device *rtc = file->private_data; void __user *uarg = (void __user *) arg; ... ... switch (cmd) { case RTC_EPOCH_SET: case RTC_SET_TIME: if (!capable(CAP_SYS_TIME)) return -EACCES; break ; case RTC_IRQP_SET: ... ... ... ...} switch (cmd) { case RTC_ALM_READ: err = rtc_read_alarm(rtc, &alarm); if (err < 0 ) return err; if (copy_to_user(uarg, &alarm.time, sizeof (tm))) return -EFAULT; break ; case RTC_ALM_SET: ... ... case RTC_RD_TIME: err = rtc_read_time(rtc, &tm); ... ... case RTC_SET_TIME: ... ... case RTC_IRQP_SET: ... ... }
这里我们假设是读RTC时间,即会进入rtc_read_time函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int rtc_read_time (struct rtc_device *rtc, struct rtc_time *tm) { int err; err = mutex_lock_interruptible(&rtc->ops_lock); if (err) return -EBUSY; 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); } mutex_unlock(&rtc->ops_lock); return err; }
从上面可以看出,rtc->ops->read_time即为rtc_device结构体下的ops成员s3c_rtcops 下的read_time函数,即s3c_rtc_getalarm函数
调用了半天,最终还是调用s3c_rtcops 下的成员函数
继续分析s3c_rtc_getalarm函数,看看如何读出时间:
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 static int s3c_rtc_gettime (struct device *dev, struct rtc_time *rtc_tm) { unsigned int have_retried = 0 ; void __iomem *base = s3c_rtc_base; retry_get_time: rtc_tm->tm_min = readb(base + S3C2410_RTCMIN); rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR); rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE); rtc_tm->tm_mon = readb(base + S3C2410_RTCMON); rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR); rtc_tm->tm_sec = readb(base + S3C2410_RTCSEC); if (rtc_tm->tm_sec == 0 && !have_retried) { have_retried = 1 ; goto retry_get_time; } BCD_TO_BIN(rtc_tm->tm_sec); BCD_TO_BIN(rtc_tm->tm_min); BCD_TO_BIN(rtc_tm->tm_hour); BCD_TO_BIN(rtc_tm->tm_mday); BCD_TO_BIN(rtc_tm->tm_mon); BCD_TO_BIN(rtc_tm->tm_year); rtc_tm->tm_year += 100 ; rtc_tm->tm_mon -= 1 ; return 0 ; }
同样, 在s3c_rtc_gettime函数下(即s3c_rtcops-> set_time()函数),也是向相关寄存器写入RTC时间
总结
rtc_device->char_dev :字符设备,与应用层、以及更底层的函数打交道
rtc_device->ops :更底层的操作函数,直接操作硬件相关的寄存器,被rtc_device->char_dev调用
4、修改内核
我们单板上使用ls /dev/rtc*
,找不到该字符设备, 因为内核里只定义了s3c_device_rtc这个RTC平台设备,没有注册,所以平台驱动没有被匹配上,接下来我们来修改内核里的注册数组
4.1进入arch/arm/plat-s3c24xx/Common-smdk.c
如下所示,在smdk_devs[]里,添加RTC的平台设备即可,当内核启动时,就会调用该数组,将里面的platform_device统统注册一遍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static struct platform_device __initdata *smdk_devs [] = { &s3c_device_nand, &smdk_led4, &smdk_led5, &smdk_led6, &smdk_led7, &s3c_device_rtc, #if defined(CONFIG_DM9000) || defined(CONFIG_DM9000_MODULE) &s3c_device_dm9k, #endif #ifdef CONFIG_SERIAL_EXTEND_S3C24xx &s3c_device_8250, #endif #ifdef CONFIG_TOUCHSCREEN_S3C2410 &s3c_device_ts, #endif };
然后将Common-smdk.c代替虚拟机的内核目录下的Common-smdk.c,重新make uImage编译内核即可
5、测试运行
启动后,如下所示, 使用ls /dev/rtc*
,就找到了rtc0这个字符设备
1 2 3 # ls /dev/rtc*/dev/rtc0 #
5.1、设置RTC时间
在linux里有两个时钟:
硬件时钟(2440里寄存器的时钟)、系统时钟(内核中的时钟)
所以有两个不同的命令: date命令、hwclock命令
5.2、date命令
输入date查看系统时钟:
1 2 # date wed Nov 3 14:50:24 UTC 2021
如果觉得不方便也可以指定格式显示日期,需要在字符串前面加”+”
如下所示,输入了 date “+ %Y/%m/%d %H:%M:%S”
1 2 3 4 5 6 # date wed Nov 3 14:54:30 UTC 2021 # # data "+ %Y/%m/%d %H:%M:%S" 2021/11/03 14:54:33
%M:表示秒
%m:表示月
%Y:表示年,当只需要最后两位数字,输入%y即可
date命令设置时间格式如下:
date 月日时分年.秒
如下所示,输入date 111515292017.20,即可设置好系统时钟
1 2 3 # date 111515292017.20wed Nov 15 15:29:20 UTC 2017 #
5.3、hwclock命令
常用参数如下所示
-r, --show 读取并打印硬件时钟(read hardware clock and print result )
-s, --hctosys 将硬件时钟同步到系统时钟(set the system time from the hardware clock )
-w, --systohc 将系统时钟同步到硬件时钟(set the hardware clock to the current system time )
如下所示,使用hwclock -w,即可同步硬件时钟
1 2 3 4 5 6 # hwclock -r Wed Nov 3 15:20:46 2021 0.000000 seconds 未同步之前的时间 # hwclock -w # hwclock -r Wed Nov 15 15:30:06 2017 0.000000 seconds 同步后的时间 #
然后重启后,使用date命令,看到时间正常