linux文件系统的系统分析--(十六)sysfs和设备模型--从platform和rtc来感受设备模型
时间:2016-02-22 13:12 来源:linux.it.net.cn 作者:IT
分析文件系统后,再分析设备模型,从而自然的理解了sysfs和设备模型的关系.既然linux2.6引入了设备模型和sysfs,那肯定是比之前更为先进的.下面要分析下内核中如何利用设备模型去管理设备的,在内核的driver目录下有好多子目录,比如iic spi pci input usb这些子系统的框架,这些框架都是在设备模型的模型之上再去抽象的.我们仅以platform这种虚拟的总线类型和rtc这种基本的类设备来感受下设备模型.具体的软硬件环境是:linux2.6.34.1和s3c2440.
platform这种总线的初始化是很早的,基本上在设备模型的核心初始化之后就开始做初始化了.在driver_init中有这么两行注释说的很明白的:
/* These are the core pieces */
/* These are also core pieces, but must come after the
* core core pieces.
*/
第一句注释指的就是devices buses classes等的初始化,而第二句注释指的就有platform bus的初始化:
[cpp] view plain copy
-
int __init platform_bus_init(void)
-
{
-
int error;
-
-
early_platform_cleanup();
-
-
error = device_register(&platform_bus);
-
if (error)
-
return error;
-
error = bus_register(&platform_bus_type);
-
if (error)
-
device_unregister(&platform_bus);
-
return error;
-
}
初始化动作很简单,初始化了名为platform的设备,在/sys/devices有了platform的目录;初始化了platform的总线,在/sys/bus目录下有了platform的目录,在platform的目录下面有:
[cpp] view plain copy
-
drwxr-xr-x 4 root root 0 2012-04-15 13:52 .
-
drwxr-xr-x 22 root root 0 2012-04-15 13:51 ..
-
drwxr-xr-x 2 root root 0 2012-04-15 13:52 devices
-
drwxr-xr-x 19 root root 0 2012-04-15 13:52 drivers
-
-rw-r--r-- 1 root root 4096 2012-04-15 13:52 drivers_autoprobe
-
--w------- 1 root root 4096 2012-04-15 13:52 drivers_probe
-
--w------- 1 root root 4096 2012-04-15 13:52 uevent
说完了platform总线的初始化,就要说说rtc类的初始化:
在drivers/rtc/class.c中:
subsys_initcall(rtc_init)-->rtc_init
[cpp] view plain copy
-
static int __init rtc_init(void)
-
{
-
rtc_class = class_create(THIS_MODULE, "rtc");
-
if (IS_ERR(rtc_class)) {
-
printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
-
return PTR_ERR(rtc_class);
-
}
-
rtc_class->suspend = rtc_suspend;
-
rtc_class->resume = rtc_resume;
-
rtc_dev_init();
-
rtc_sysfs_init(rtc_class);
-
return 0;
-
}
主要做了三个工作:1.rtc class的注册,在/sys/class下有了rtc目录;2.rtc_dev_init();获取设备号:rtc_devt;3.在rtc_sysfs_init中将rtc_class的dev_attrs类设备属性(struct device_attribute *dev_attrs)指向了rtc_attrs
rtc_attrs的定义如下:
[cpp] view plain copy
-
static struct device_attribute rtc_attrs[] = {
-
__ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL),
-
__ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL),
-
__ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL),
-
__ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL),
-
__ATTR(max_user_freq, S_IRUGO | S_IWUSR, rtc_sysfs_show_max_user_freq,
-
rtc_sysfs_set_max_user_freq),
-
__ATTR(hctosys, S_IRUGO, rtc_sysfs_show_hctosys, NULL),
-
{ },
-
};
类设备属性文件何时会建立,前面在分析devices时,有device_add-->device_add_attrs,在device_add_attrs函数中,如果device从属于某个class的话,就会建立这些类设备
属性文件.这些类设备属性文件放在/sys/device下某个具体设备目录下面.这些类设备属性基本上都是可读的.
分析完platform bus和rtc class后,对于s3c2440下的rtc,还有哪些具体的工作要做呢?just go on!!
先看下platform_device结构体在device结构体之上的封装:
[cpp] view plain copy
-
struct platform_device {
-
const char * name;
-
int id;
-
struct device dev;
-
u32 num_resources;
-
struct resource * resource;
-
-
const struct platform_device_id *id_entry;
-
-
/* arch specific additions */
-
struct pdev_archdata archdata;
-
};
定义s3c2440 rtc这个platform_device如下:
[cpp] view plain copy
-
struct platform_device s3c_device_rtc = {
-
.name = "s3c2410-rtc",
-
.id = -1,
-
.num_resources = ARRAY_SIZE(s3c_rtc_resource),
-
.resource = s3c_rtc_resource,
-
};
resource就包含了s3c2440 rtc的寄存器资源和中断资源.
platform_device的注册是在平台初始化时完成的:
smdk2410_init-->platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
platform_add_devices函数用于添加一组platform_device,而smdk2410_devices是一个数组:
static struct platform_device *smdk2410_devices[],数组的其中一项就是s3c_device_rtc,这样在
platform_add_devices-->platform_device_register
[cpp] view plain copy
-
int platform_device_register(struct platform_device *pdev)
-
{
-
device_initialize(&pdev->dev);
-
return platform_device_add(pdev);
-
}
该函数也是分为两步,和device_register类似,先调用device_initialize初始化内嵌的device结构体,然后在platform_device_add函数中调用device_add函数添加内嵌的device结构体.当然platform_device_add在调用device_add之前做了很多工作:
[cpp] view plain copy
-
if (!pdev->dev.parent)
-
pdev->dev.parent = &platform_bus;
-
-
pdev->dev.bus = &platform_bus_type;
-
-
if (pdev->id != -1)
-
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
-
else
-
dev_set_name(&pdev->dev, "%s", pdev->name);
确定内嵌的device结构体的parent指向platform_bus,这个platform_bus就是前面在platform bus初始化时注册的一个设备;
确定内嵌的device结构体的bus指向platform_bus_type
确定内嵌的device结构体的name,这里就是s3c2410-rtc
[cpp] view plain copy
-
for (i = 0; i < pdev->num_resources; i++) {
-
struct resource *p, *r = &pdev->resource[i];
-
-
if (r->name == NULL)
-
r->name = dev_name(&pdev->dev);
-
-
p = r->parent;
-
if (!p) {
-
if (resource_type(r) == IORESOURCE_MEM)
-
p = &iomem_resource;
-
else if (resource_type(r) == IORESOURCE_IO)
-
p = &ioport_resource;
-
}
-
-
if (p && insert_resource(p, r)) {
-
printk(KERN_ERR
-
"%s: failed to claim resource %d\n",
-
dev_name(&pdev->dev), i);
-
ret = -EBUSY;
-
goto failed;
-
}
-
}
这个for循环就是将platform device的resouce管理起来,大概瞄了一眼,应该是通过tree来管理各类resouce.
最后才是调用device_add函数完成内嵌device结构体在设备模型体系中的添加操作.值得一提的是:device_add会调用bus_add_device,
bus_add_device会建立一些链接文件和属性文件,并且将设备加入到总线的设备链表中:
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);这个链表稍后就会看到driver注册是如何用到的.
在bus_add_device之后还有一个重要步骤:bus_probe_device 为新增加的设备匹配driver:
bus_probe_device-->device_attach-->bus_for_each_drv
遍历bus->p->klist_drivers总线的drivers链表,把这个设备和每个driver都来用__device_attach函数匹配一下试试看:
__device_attach首先会调用driver_match_device做个简单的匹配操作,这个匹配操作是由bus级的match函数来做的,对于platform总线,这个match函数很简单:
platform_match-->比较设备的名称和driver的名称是否一致,如果名称一致的话,再调用driver_probe_device(drv, dev);
driver_probe_device-->really_probe(dev, drv);
really_probe函数作用如下:
1.dev->driver = drv; //device结构体的driver指针指向device_driver,证明这个device和这个driver匹配好了
2.driver_sysfs_add(dev) //在sysfs中建立从driver指向device的链接文件
3.如果总线级别的probe函数指针存在,就调用总线级别的probe函数,否则如果driver级别的probe函数指针存在,就调用driver级别
的probe函数,对于platform总线,不存在probe,所以这里调用driver级别的probe函数.对于s3c2440 rtc这个具体情况,这里的probe
函数是s3c_rtc_probe,这个函数指针的获取过程还蛮曲折:
(
[cpp] view plain copy
-
platform_driver_register中因为platform_driver级别的probe函数:s3c_rtc_probe存在,所以内嵌的driver级别的
[cpp] view plain copy
-
probe指向platform_drv_probe,而在platform_drv_probe中实际还是调用platform_driver级别的probe函数.绕了一个
[cpp] view plain copy
-
圈子,最后调用的就是s3c_rtc_probe
)
s3c_rtc_probe这个函数到后面再讲.
看完platform_device后看platform_driver:
[cpp] view plain copy
-
struct platform_driver {
-
int (*probe)(struct platform_device *);
-
int (*remove)(struct platform_device *);
-
void (*shutdown)(struct platform_device *);
-
int (*suspend)(struct platform_device *, pm_message_t state);
-
int (*resume)(struct platform_device *);
-
struct device_driver driver;
-
const struct platform_device_id *id_table;
-
};
platform_driver是在struct device_driver driver;之上封装了自己的方法:
s3c2410_rtc_driver的定义如下:
[cpp] view plain copy
-
static struct platform_driver s3c2410_rtc_driver = {
-
.probe = s3c_rtc_probe,
-
.remove = __devexit_p(s3c_rtc_remove),
-
.suspend = s3c_rtc_suspend,
-
.resume = s3c_rtc_resume,
-
.driver = {
-
.name = "s3c2410-rtc",
-
.owner = THIS_MODULE,
-
},
-
};
该platform_driver的注册过程如下:
module_init(s3c_rtc_init);-->s3c_rtc_init-->platform_driver_register(&s3c2410_rtc_driver);
[cpp] view plain copy
-
/**
-
* platform_driver_register - register a driver for platform-level devices
-
* @drv: platform driver structure
-
*/
-
int platform_driver_register(struct platform_driver *drv)
-
{
-
drv->driver.bus = &platform_bus_type;
-
if (drv->probe)
-
drv->driver.probe = platform_drv_probe;
-
if (drv->remove)
-
drv->driver.remove = platform_drv_remove;
-
if (drv->shutdown)
-
drv->driver.shutdown = platform_drv_shutdown;
-
-
return driver_register(&drv->driver);
-
}
首先将内嵌的device_driver结构体的bus指针指向platform_bus_type;如果platform_driver有probe remove shutdown函数指针,就将
其内嵌的device_driver结构体的probe remove shutdown函数指针指向platform_drv_probe platform_drv_remove和platform_drv_shutdown
做完上面的工作后,再调用driver_register(&drv->driver)注册内嵌的device_driver.
device_driver在分析driver的时候有讲过,只不过当时没讲的很透,原因就是没有具体的bus可以来讲,现在条件成熟了:
driver_register-->bus_add_driver-->driver_attach
[cpp] view plain copy
-
int driver_attach(struct device_driver *drv)
-
{
-
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
-
}
就会调用bus_for_each_dev函数对bus上bus->p->klist_devices挂着的所有device,都调用__driver_attach来试着匹配.在前面我们已经看到
[cpp] view plain copy
-
s3c_device_rtc已经加入到了这个链表中.
[cpp] view plain copy
-
其实这里的__driver_attach和前面的__device_attach很类似:
[cpp] view plain copy
-
1.同样调用driver_match_device函数做一个检验
[cpp] view plain copy
-
2.调用driver_probe_device函数来做device和driver的最终匹配操作.
殊途同归,都指向了前面提到的s3c_rtc_probe,现在再来分析该函数:
在s3c_rtc_probe中我们定义了一个rtc_device:struct rtc_device *rtc;
然后是各种中断资源 IO资源等的获取和映射之类的操作,最后就是注册RTC:
[cpp] view plain copy
-
/* register RTC and exit */
-
-
rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
-
THIS_MODULE);
-
-
if (IS_ERR(rtc)) {
-
dev_err(&pdev->dev, "cannot attach rtc\n");
-
ret = PTR_ERR(rtc);
-
goto err_nortc;
-
}
-
-
rtc->max_user_freq = 128;
-
-
platform_set_drvdata(pdev, rtc);
rtc_device_register函数是注册rtc类设备的函数,只要是rtc,那不管你是soc上带的rtc,还是iic访问的rtc,抑或是spi方位的rtc,都要调用这个函数来注册rtc设备,而这也正好说明 了class的含义.
最后的工作就是仔细分析这个rtc_device_register:
1.定义一个struct rtc_device *rtc;结构体并初始化:
[cpp] view plain copy
-
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的ops指向了s3c_rtcops操作函数结构体,
[cpp] view plain copy
-
rtc->dev.parent = dev; 也就是父设备是s3c2410-rtc这个平台设备内嵌的device
[cpp] view plain copy
-
rtc->dev.class = rtc_class; rtc设备内嵌的device的属于rtc_class
[cpp] view plain copy
-
strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE); //rtc设备的名称设为"s3c"
-
yle="white-space:pre"> </span>dev_set_name(&rtc->dev, "rtc%d", id); //rtc设备内嵌的device的名称设置为rtc+id,id从0开始
[cpp] view plain copy
-
rtc_dev_prepare:
[cpp] view plain copy
-
rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id); //设置devt,rtc_devt是在rtc_init分配好的
[cpp] view plain copy
-
cdev_init(&rtc->char_dev, &rtc_dev_fops); //初始化rtc设备内嵌的字符设备结构体char_dev
2.err = device_register(&rtc->dev);
在设备模型中注册rtc设备内嵌的device
3.rtc_dev_add_device(rtc);
调用cdev_add注册rtc内嵌的字符设备char_dev,用到的设备号是rtc->dev.devt,这个字符设备的操作方法是在rtc-dev.c中定义的:
[cpp] view plain copy
-
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,
-
.open = rtc_dev_open,
-
.release = rtc_dev_release,
-
.fasync = rtc_dev_fasync,
-
};
这个内嵌字符设备的注册,那么在/dev中就有rtc的字符设备节点文件了.
4.rtc_sysfs_add_device(rtc);
在sysfs下创建wakealarm设备属性文件
5.rtc_proc_add_device(rtc);
在proc目录下创建/proc/driver/rtc文件,这个文件的file_operations如下:
[cpp] view plain copy
-
static const struct file_operations rtc_proc_fops = {
-
.open = rtc_proc_open,
-
.read = seq_read,
-
.llseek = seq_lseek,
-
.release = rtc_proc_release,
-
};
到此,s3c2440的rtc才加入了linux的设备模型,但是我们也看到了一大堆的操作方法,到底这些操作方法到底怎么用?
再次罗列一下刚才出做的操作方法:
static const struct rtc_class_ops s3c_rtcops //rtc设备的ops(const struct rtc_class_ops *ops)指向该方法
static const struct file_operations rtc_dev_fops // /dev目录下rtc字符设备文件的文件操作方法
static const struct file_operations rtc_proc_fops // /proc/driver/rtc文件的文件操作方法
上面有这么三类方法,仔细想一想,最基本的或者说实际的应该是s3c_rtcops,因为只有这个方法集合中的函数才有与硬件寄存器
打交道的过程,那么/dev下面的rtc_dev_fops和 /proc/driver/rtc的rtc_proc_fops的各种方法必然依赖于这个rtc_class_ops的具体
实现,当然sys下面的属性文件的显示方法也是最终依赖于它的.
换一个方法来看,来看makefile和kconfig文件:
在drivers/rtc下面,截取makefile和kconfig文件如下:
rtc-core-y := class.o interface.o
rtc-core-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o
rtc-core-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o
rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
interface.c名为接口,但到目前为止我们尚未分析它,但它和class.c都是必须编译进kernel的,说明它很重要.
而rtc-dev.c rtc-proc.c rtc-sysfs.c这三个文件是与/dev /proc/driver/rtc 和 /sys 相关的,看看kconfig的注释就明白了:
这三处都是作为用户空间访问rtc设备的接口,什么接口?linux下一切都是文件,而这里的文件接口提供了三个.
我们以/dev下的文件接口为例:
既然是rtc,那最起码要能设置和读取时间,以interface.c中的rtc_set_time为例,
rtc_set_time-->rtc->ops->set_time,也就是s3c_rtc_settime函数真正的干活
而/dev下的rtc字符设备的操作函数中,我们是通过static const struct file_operations rtc_dev_fops
中的.unlocked_ioctl = rtc_dev_ioctl, ioctl来调用rtc_set_time的
[cpp] view plain copy
-
case RTC_SET_TIME:
-
mutex_unlock(&rtc->ops_lock);
-
-
if (copy_from_user(&tm, uarg, sizeof(tm)))
-
return -EFAULT;
-
-
return rtc_set_time(rtc, &tm);
到这里,s3c2440的platform总线和rtc类设备终于较为完整的梳理了一遍,对于proc和sysfs文件的读写方法最终还是要依赖与
rtc的类操作集合,具体的就不再赘述了.另外,busybox中提供hwclock命令来对时间操作,这个与rtc是息息相关的,研究一下也挺
有意思的.
最后,对这一次一齐出现的bus device driver 和class做一个总结:
1.利用设备的模型的设备驱动和char_dev字符设备驱动完全两回事,虽然有交叉的地方,但差距相当大.在构造字符设备驱动的时候,需要做的是
分配设备号,构造设备结构体,构造字符设备的操作集合--file_operations,基本上所有的与硬件打交道的行为都在这里完成.而在上面的分析中,
platform_bus是用来管理特定平台的device和driver的,platform_device包含了特定硬件设备的信息,而platform_driver则包含了一些方法.而
class是一个对同一类设备的抽象.
2.设备模型的好处,从一路分析过来的体会来看:首先用设备模型搭建的框架使得代码的复用性更强:platform可以支持rtc iic framebuffer等多种
设备,rtc类设备也支持soc中的rtc,spi访问的rtc或者iic访问的rtc.复用性好,代码更强大.
3.作为一个虚拟的总线还是比较简单的,但还是可以帮助我在整体上把握设备模型,为以后分析iic input pci usb等复杂的子系统打下基础.
(责任编辑:IT)
分析文件系统后,再分析设备模型,从而自然的理解了sysfs和设备模型的关系.既然linux2.6引入了设备模型和sysfs,那肯定是比之前更为先进的.下面要分析下内核中如何利用设备模型去管理设备的,在内核的driver目录下有好多子目录,比如iic spi pci input usb这些子系统的框架,这些框架都是在设备模型的模型之上再去抽象的.我们仅以platform这种虚拟的总线类型和rtc这种基本的类设备来感受下设备模型.具体的软硬件环境是:linux2.6.34.1和s3c2440. platform这种总线的初始化是很早的,基本上在设备模型的核心初始化之后就开始做初始化了.在driver_init中有这么两行注释说的很明白的: /* These are the core pieces */
/* These are also core pieces, but must come after the 第一句注释指的就是devices buses classes等的初始化,而第二句注释指的就有platform bus的初始化:
[cpp] view plain copy
初始化动作很简单,初始化了名为platform的设备,在/sys/devices有了platform的目录;初始化了platform的总线,在/sys/bus目录下有了platform的目录,在platform的目录下面有:
[cpp] view plain copy
在drivers/rtc/class.c中: subsys_initcall(rtc_init)-->rtc_init
[cpp] view plain copy
rtc_attrs的定义如下:
[cpp] view plain copy
属性文件.这些类设备属性文件放在/sys/device下某个具体设备目录下面.这些类设备属性基本上都是可读的.
分析完platform bus和rtc class后,对于s3c2440下的rtc,还有哪些具体的工作要做呢?just go on!! 先看下platform_device结构体在device结构体之上的封装:
[cpp] view plain copy
定义s3c2440 rtc这个platform_device如下:
[cpp] view plain copy
platform_device的注册是在平台初始化时完成的:
smdk2410_init-->platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); static struct platform_device *smdk2410_devices[],数组的其中一项就是s3c_device_rtc,这样在 platform_add_devices-->platform_device_register
[cpp] view plain copy
[cpp] view plain copy
确定内嵌的device结构体的bus指向platform_bus_type 确定内嵌的device结构体的name,这里就是s3c2410-rtc
[cpp] view plain copy
最后才是调用device_add函数完成内嵌device结构体在设备模型体系中的添加操作.值得一提的是:device_add会调用bus_add_device, bus_add_device会建立一些链接文件和属性文件,并且将设备加入到总线的设备链表中: klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);这个链表稍后就会看到driver注册是如何用到的. 在bus_add_device之后还有一个重要步骤:bus_probe_device 为新增加的设备匹配driver: bus_probe_device-->device_attach-->bus_for_each_drv 遍历bus->p->klist_drivers总线的drivers链表,把这个设备和每个driver都来用__device_attach函数匹配一下试试看: __device_attach首先会调用driver_match_device做个简单的匹配操作,这个匹配操作是由bus级的match函数来做的,对于platform总线,这个match函数很简单: platform_match-->比较设备的名称和driver的名称是否一致,如果名称一致的话,再调用driver_probe_device(drv, dev); driver_probe_device-->really_probe(dev, drv); really_probe函数作用如下: 1.dev->driver = drv; //device结构体的driver指针指向device_driver,证明这个device和这个driver匹配好了 2.driver_sysfs_add(dev) //在sysfs中建立从driver指向device的链接文件 3.如果总线级别的probe函数指针存在,就调用总线级别的probe函数,否则如果driver级别的probe函数指针存在,就调用driver级别 的probe函数,对于platform总线,不存在probe,所以这里调用driver级别的probe函数.对于s3c2440 rtc这个具体情况,这里的probe 函数是s3c_rtc_probe,这个函数指针的获取过程还蛮曲折: (
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
s3c_rtc_probe这个函数到后面再讲.
看完platform_device后看platform_driver:
[cpp] view plain copy
s3c2410_rtc_driver的定义如下:
[cpp] view plain copy
module_init(s3c_rtc_init);-->s3c_rtc_init-->platform_driver_register(&s3c2410_rtc_driver);
[cpp] view plain copy
其内嵌的device_driver结构体的probe remove shutdown函数指针指向platform_drv_probe platform_drv_remove和platform_drv_shutdown device_driver在分析driver的时候有讲过,只不过当时没讲的很透,原因就是没有具体的bus可以来讲,现在条件成熟了: driver_register-->bus_add_driver-->driver_attach
[cpp] view plain copy
就会调用bus_for_each_dev函数对bus上bus->p->klist_devices挂着的所有device,都调用__driver_attach来试着匹配.在前面我们已经看到
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
殊途同归,都指向了前面提到的s3c_rtc_probe,现在再来分析该函数: 在s3c_rtc_probe中我们定义了一个rtc_device:struct rtc_device *rtc; 然后是各种中断资源 IO资源等的获取和映射之类的操作,最后就是注册RTC:
[cpp] view plain copy
rtc_device_register函数是注册rtc类设备的函数,只要是rtc,那不管你是soc上带的rtc,还是iic访问的rtc,抑或是spi方位的rtc,都要调用这个函数来注册rtc设备,而这也正好说明 了class的含义.
最后的工作就是仔细分析这个rtc_device_register: 1.定义一个struct rtc_device *rtc;结构体并初始化:
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
[cpp] view plain copy
在设备模型中注册rtc设备内嵌的device 3.rtc_dev_add_device(rtc); 调用cdev_add注册rtc内嵌的字符设备char_dev,用到的设备号是rtc->dev.devt,这个字符设备的操作方法是在rtc-dev.c中定义的:
[cpp] view plain copy
4.rtc_sysfs_add_device(rtc);
在sysfs下创建wakealarm设备属性文件 在proc目录下创建/proc/driver/rtc文件,这个文件的file_operations如下:
[cpp] view plain copy
到此,s3c2440的rtc才加入了linux的设备模型,但是我们也看到了一大堆的操作方法,到底这些操作方法到底怎么用? 再次罗列一下刚才出做的操作方法: static const struct rtc_class_ops s3c_rtcops //rtc设备的ops(const struct rtc_class_ops *ops)指向该方法 static const struct file_operations rtc_dev_fops // /dev目录下rtc字符设备文件的文件操作方法 static const struct file_operations rtc_proc_fops // /proc/driver/rtc文件的文件操作方法
上面有这么三类方法,仔细想一想,最基本的或者说实际的应该是s3c_rtcops,因为只有这个方法集合中的函数才有与硬件寄存器 打交道的过程,那么/dev下面的rtc_dev_fops和 /proc/driver/rtc的rtc_proc_fops的各种方法必然依赖于这个rtc_class_ops的具体 实现,当然sys下面的属性文件的显示方法也是最终依赖于它的.
换一个方法来看,来看makefile和kconfig文件: 在drivers/rtc下面,截取makefile和kconfig文件如下:
rtc-core-y := class.o interface.o 而rtc-dev.c rtc-proc.c rtc-sysfs.c这三个文件是与/dev /proc/driver/rtc 和 /sys 相关的,看看kconfig的注释就明白了:
这三处都是作为用户空间访问rtc设备的接口,什么接口?linux下一切都是文件,而这里的文件接口提供了三个. 既然是rtc,那最起码要能设置和读取时间,以interface.c中的rtc_set_time为例, rtc_set_time-->rtc->ops->set_time,也就是s3c_rtc_settime函数真正的干活 而/dev下的rtc字符设备的操作函数中,我们是通过static const struct file_operations rtc_dev_fops 中的.unlocked_ioctl = rtc_dev_ioctl, ioctl来调用rtc_set_time的
[cpp] view plain copy
到这里,s3c2440的platform总线和rtc类设备终于较为完整的梳理了一遍,对于proc和sysfs文件的读写方法最终还是要依赖与
rtc的类操作集合,具体的就不再赘述了.另外,busybox中提供hwclock命令来对时间操作,这个与rtc是息息相关的,研究一下也挺 有意思的.
最后,对这一次一齐出现的bus device driver 和class做一个总结: 1.利用设备的模型的设备驱动和char_dev字符设备驱动完全两回事,虽然有交叉的地方,但差距相当大.在构造字符设备驱动的时候,需要做的是 分配设备号,构造设备结构体,构造字符设备的操作集合--file_operations,基本上所有的与硬件打交道的行为都在这里完成.而在上面的分析中, platform_bus是用来管理特定平台的device和driver的,platform_device包含了特定硬件设备的信息,而platform_driver则包含了一些方法.而 class是一个对同一类设备的抽象. 2.设备模型的好处,从一路分析过来的体会来看:首先用设备模型搭建的框架使得代码的复用性更强:platform可以支持rtc iic framebuffer等多种 设备,rtc类设备也支持soc中的rtc,spi访问的rtc或者iic访问的rtc.复用性好,代码更强大. 3.作为一个虚拟的总线还是比较简单的,但还是可以帮助我在整体上把握设备模型,为以后分析iic input pci usb等复杂的子系统打下基础. (责任编辑:IT) |