Linux|RTC驱动模型分析

①RTC设备层:
设备资源的定义:arch/arm/plat-s3c24xx/devs.c

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,//## };

arch/arm/mach-s3c2440/mach-tq2440.c
static struct platform_device *tq2440_devices[] __initdata = https://www.it610.com/article/{ ......, &s3c_device_rtc, //## ......, };

tq2440_devices[]这个数组在哪里被使用?这意味着RTC设备在哪里被注册。它是个静态数组,就在该文件找就行了。
tq2440_machine_init(void)
platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices)); //注意:是platform_add_devices是通过for循环调用platform_devices_add()函数
//通过这个函数就把这个数组里边所有的设备资源都注册到了platform设备总线下
根据宏的展开:
MACHINE_START(S3C2440, "TQ2440")
......
.init_machine = tq2440_machine_init,//这个函数是被放在“.arch.info.init”段,在加载内核时会被自动调用
......
MACHINE_END

--------------------------------------------------------------------------------------------------------------------------------------------------------------
②RTC驱动层:
drivers/rtc/rtc-s3c.c
static struct platform_driver s3c2410_rtc_driver = { .probe= s3c_rtc_probe, ...... .driver= { .name = "s3c2410-rtc", .owner = THIS_MODULE, }, };

函数调用关系:
s3c_rtc_init(void)
return platform_driver_register(&s3c2410_rtc_driver); //这样就把RTC驱动注册到platform总线上去了
看看驱动层的探针函数的实现:
s3c_rtc_probe(struct platform_device *pdev) { ...... 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_base = ioremap(res->start, res->end - res->start + 1); ...... s3c_rtc_enable(pdev, 1); ...... s3c_rtc_setfreq(&pdev->dev, 1); ...... rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE); //【进去分析】 }

当我进去分析rtc_device_register()的实现时让我纳闷的是:
按照前面分析platform驱动模型的那种节奏,现在这种时候probe函数应该要完成注册字符设备、实现file_operation为应用层提供API之类的代码,却偏偏莫名其妙地来了个rtc_device_register()函数,给人的感觉貌似又是注册一个设备,若说是添加RTC设备在设备层已经有platform_add_devices函数把资源注册到platform总线上了,蛋疼!
这个函数完成的事情跟platform_add_devices函数究竟有什么区别呢?看来还得研究内核源码!里面肯定藏着不可告人的秘密!
就从rtc_device_register这个函数入手试试看:
/drivers/rtc/class.c
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); //这个函数在/drivers/rtc/rtc-dev.c:定义 //②把这个结构的dev结构成员往上注册 err = device_register(&rtc->dev); //这个函数最终就是调用device_add(&pdev->dev); ...... rtc_dev_add_device(rtc); rtc_sysfs_add_device(rtc); rtc_proc_add_device(rtc); ...... }

且看rtc_device结构体:
/include/linux/rtc.h
struct rtc_device { struct device dev; struct module *owner; int id; char name[RTC_DEVICE_NAME_SIZE]; ....... const struct rtc_class_ops *ops; //## struct mutex ops_lock; ....... struct cdev char_dev; unsigned long flags; int max_user_freq; }

在/drivers/rtc/rtc-s3c.c
static const struct rtc_class_ops s3c_rtcops = { .open= s3c_rtc_open, .release = s3c_rtc_release, .read_time = s3c_rtc_gettime, .set_time = s3c_rtc_settime, .read_alarm = s3c_rtc_getalarm, .set_alarm = s3c_rtc_setalarm, .irq_set_freq = s3c_rtc_setfreq, .irq_set_state = s3c_rtc_setpie, .proc= s3c_rtc_proc, };

这个结构体的成员函数都是实实在在地要操作底层硬件的函数。
回去看rtc_device_register函数的第②点,仔细一想突然好像想明白了什么,赶快写下来:
根据经验,我们通常在file_operation结构体为APP提供的API函数接口实现中直接进行操作底层硬件的操作,比如前一篇博文中基于平台的led的write()是通过操作GPBDAT寄存器来进行开关led的,那时我们是这么干的:注册字符设备驱动时把file_operation结构体往这个函数一塞就挂到内核里边去了,现在还不是一样么,把这些操作硬件的函数放到rtc_class_ops结构中,不同的是它被rtc_device结构体所包含,同时rtc_device结构体也包含一个内核device结构体,然后通过device_register()把这个device往上注册,应用层想要操作硬件的某种行为完全是可以
这一系列依据来找到rtc_class_ops结构中的这些底层操作函数的。也就是说,以前我们习惯把操作硬件的函数放到file_operation结构体中然后往上注册,感觉一步到位,直接干脆洒脱!
现在rtc设备驱动中似乎是把这些底层操作函数交给了一个中介结构体rtc_class_ops,然后这个结构体又交给了另一个中介结构体rtc_device,这个中介结构体有一个能够代表他自己的结构体device,只要访问到这个device结构体最终不就找到了底层操作函数。豁然开朗!至于谁在哪里会访问到device结构体,直觉告诉我肯定还有一个层次结构!

----------------------------------------------------------------------------------------------------
③RTC核心层:他是platform核心层的子类核心层【这个结论其实是最后才得出来的,提前放在这里意在暗示结局 】
在/drivers/rtc/rtc-dev.c:
static const struct file_operations rtc_dev_fops = { .owner= THIS_MODULE, .llseek= no_llseek, .read= rtc_dev_read, .poll= rtc_dev_poll, .unlocked_ioctl = rtc_dev_ioctl,//**ioctl()函数里边命令的响应函数在/drivers/rtc/intreface.c中定义 .open= rtc_dev_open, .release = rtc_dev_release, .fasync= rtc_dev_fasync, };

看看这个file_operation在哪里被使用:
rtc_dev_prepare(struct rtc_device *rtc) { ...... rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); ...... cdev_init(&rtc->char_dev, &rtc_dev_fops); //在这里被用来注册字符设备 rtc->char_dev.owner = rtc->owner; }

那rtc_dev_prepare()这个函数在什么时候在什么地方被调用?
还记得在分析驱动层时的rtc_device_register()函数不,就是它,它是为驱动层提供的注册接口函数:
/drivers/rtc/class.c
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->dev.class = rtc_class; ...... rtc_dev_prepare(rtc); //## //②把这个结构的dev结构成员往上注册 err = device_register(&rtc->dev); //这个函数最终就是调用device_add(&pdev->dev); ...... rtc_dev_add_device(rtc); ...... }

分析到这里RTC驱动的主线工作路线就呈现出来了,但有一个问题自然会想到,在RTC驱动层和设备层已经把RTC驱动
和设备资源都注册到platform平台了,驱动层的probe函数不是直接注册字符设备实现API接口不就OK了么?这样做有
什么作用?
分析:先明确一下,rtc驱动的probe函数多做了哪些事情:
再次来看看rtc_device_register()函数
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->ops = ops; ...... rtc->dev.class = rtc_class; rtc_dev_prepare(rtc); //这个函数在最终完成字符设备的注册 //②从根源的角度来说以下就是probe函数最终多做的事情 err = device_register(&rtc->dev); //这个函数最终就是调用device_add(&pdev->dev); ...... rtc_dev_add_device(rtc); rtc_sysfs_add_device(rtc); rtc_proc_add_device(rtc); ...... }

在/drivers/rtc/class.c
rtc_init(void) { rtc_class = class_create(THIS_MODULE, "rtc"); /* 创建了一个类--rtc */ ...... rtc_class->suspend = rtc_suspend; rtc_class->resume = rtc_resume; rtc_dev_init(); /* 为RTC设备动态分配设备号 */ rtc_sysfs_init(rtc_class); return 0; }

这个函数在linux设备模型sysfs下创建了一个rtc的类,那么肯定有一个地方会往这个类中添加设备,按照这种推理
那么上边说到的“多余”的工作应该就是它了。那就来分析一下device_register()函数究竟会做哪些事情?
device_register(&rtc->dev)
error = device_add_class_symlinks(dev);
就是这个函数了:
static int device_add_class_symlinks(struct device *dev)//从函数的名字猜测:在类下创建设备的符号链接文件 { int error; ...... error = sysfs_create_link(&dev->kobj,&dev->class->p->class_subsys.kobj,"subsystem"); //创建链接文件 ...... class_name = make_class_name(dev->class->name,&dev->kobj); //设置类下的设备名 }

如此,我们上面关心的问题就都解决了,多做这些事情无非是想把rtc归为一个类,所有这种设备都会统一添加到这rtc类中
最后来个高度总结:
1.RTC驱动框架图:
Linux|RTC驱动模型分析
文章图片


这幅图是网上找的,前辈们把其中的一些工作归结为RTC核心层,现在觉得这样也挺好理解,只是图中RTC核心层中的工作
不单单是图中列出来的那几个,因为还有platform驱动模型的核心层,个人觉得还是要把他也囊括进去才能体现出RTC驱动
的本质所在!
2.万变不离其宗,rtc驱动框架的根本还是依赖于platform驱动模型。
3.有时间尽量多看看Linux的源码,只有源码才是解开心头疑问的最有力的根据!

    推荐阅读