①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驱动框架图:
文章图片
这幅图是网上找的,前辈们把其中的一些工作归结为RTC核心层,现在觉得这样也挺好理解,只是图中RTC核心层中的工作
不单单是图中列出来的那几个,因为还有platform驱动模型的核心层,个人觉得还是要把他也囊括进去才能体现出RTC驱动
的本质所在!
2.万变不离其宗,rtc驱动框架的根本还是依赖于platform驱动模型。
3.有时间尽量多看看Linux的源码,只有源码才是解开心头疑问的最有力的根据!
推荐阅读
- Linux|109 个实用 shell 脚本
- linux笔记|linux 常用命令汇总(面向面试)
- Linux|Linux--网络基础
- linux|apt update和apt upgrade命令 - 有什么区别()
- linux|2022年云原生趋势
- Go|Docker后端部署详解(Go+Nginx)
- 开源生态|GPL、MIT、Apache...开发者如何选择开源协议(一文讲清根本区别)
- GitHub|7 款可替代 top 命令的工具