www.ourkernel.com 我们的内核
每一个组被一个void*id所标识。它既可以显式的通过devres_open_group()中的参数id来指定,也可以像上面的例子一样通过传递一个NULL给void *id来自动创建。这两种方式,devres_open_group()都会返回一个组id。这个被返回的ID可以被传递给其他的设备资源函数用来选择目标组。如果NULL参数指定给那些函数,那么最近打开的组将会被选择。
举例来说,你可以像下面一样来做一些事。
int my_midlayer_create_something() {
if (!devres_open_group(dev, my_midlayer_create_something, GFP_KERNEL)) return -ENOMEM; ...
devres_close_group(dev, my_midlayer_create_something);
return 0; }
void my_midlayer_destroy_something() {
devres_release_group(dev, my_midlayer_create_soemthing); }
4. 详细内容
-----------
一个设备资源入口的生命周期在设备资源分配时开始,而结束于它被释放或者是被销毁(删除和释放)的时候 - 没有涉及计算。
设备资源核心保证所有对设备资源的基本操作同时完成,并且支持单实例设备资源类型(下面是一系列操作(原子操作,不可分割的):检索,如果没有发现就添加)。除了那些,同步那些需要同时去存取被分配的设备资源数据的进程是调用者的责任。这通常不用去讨论,因为总线ops和资源分配已经做了这个工作。
对单实例设备资源类型这个例子来说,可以去lib/devres.c中阅读关于pcim_iomap_table()的代码。
如果正确的gfp mask被给定,那么所有的设备资源接口函数都可以不需要上文就能被调用。
5.费用 ------
21
www.ourkernel.com 我们的内核
每个设备资源记账信息会和被请求的数据一起被分配。在调试选项关闭时,在32位机器上记账信息占16个字节,而在64位机器上占24个字节。如果链表被单独使用,它可以减少到2个指针(在32位机器为8个字节,64位为16个字节)。
每个设备资源组占有8个指针。如果链表被单独使用,它能被减少到6个。
经过本地转化(毫无疑问我们需要付出更多的努力来转化成libata核心层)后,在32位机器上,在AHCI控制器使用两个端口时,内存空间花费为300-400个字节之间。
6.管理级别接口列表 ------------------
IO region
devm_request_region() devm_request_mem_region() devm_release_region()
devm_release_mem_region() IRQ
devm_request_irq() devm_free_irq()
DMA
dmam_alloc_coherent() dmam_free_coherent()
dmam_alloc_noncoherent() dmam_free_noncoherent()
dmam_declare_coherent_memory() dmam_pool_create() dmam_pool_destroy()
PCI
pcim_enable_device() : 执行成功后,所有的PCI ops都能被管理。
pcim_pin_device() : keep PCI device enabled after release经过释放后能保持PCI设备被激活
IOMAP
devm_ioport_map() devm_ioport_unmap() devm_ioremap()
devm_ioremap_nocache() devm_iounmap()
22
www.ourkernel.com 我们的内核
pcim_iomap()
pcim_iounmap()
pcim_iomap_table() : 一个被BAR索引的映射地址的数组 pcim_iomap_regions() : 在多个BARS上执行request_region()和iomap()
Driver
完成: 设备驱动程序. struct device_driver {
char * name; struct bus_type * bus;
struct completion unloaded; struct kobject kobj; list_t devices;
struct module *owner;
int (*probe) (struct device * dev); int (*remove) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_t state); int (*resume) (struct device * dev); };
分配
设备驱动程序是静态分配的结构.虽然在一个系统中可能有一个设备支持很多驱动,但是在设备驱动的数据结构表现为一个整体.
初始化
设备的名字和总线必须初始化.它也应该初始化the devclass field (当它到达的时候),它才能在核心获得恰当的连接.
它也应该初始化尽可能多的回传包,尽管其中的每一个包都是潜在的.
23
www.ourkernel.com 我们的内核
声明
如上所述,设备驱动的数据结构是静态分配的.这下面是一个描述eepro100设备的例子 . 这些声明仅仅是假设的;它依赖被完全转换到新模式的设备.
static struct device_driver eepro100_driver = { .name .bus .probe
= \= &pci_bus_type,
= eepro100_probe, = eepro100_remove, = eepro100_suspend, = eepro100_resume,
.remove .suspend .resume };
很多的设备将不能完全的转换为新模型,因为总线.总线有一个特殊的总线结构和区域,这些特殊的结构和区域兼容性不强(适应能力不强)
最常见的例子是设备的ID结构.一个驱动程序典型的配置是一个所支持的驱动id数组,这些结构和字符集的格式用来比较驱动id号是否完全匹配这些指定的端口,定义他们作为特定端口实体将以安全级别为代价,因此我们必须保留指定的端口
特殊的总线驱动应该包括定义了一个一般的总线设备驱动数据结构.如下:
struct pci_driver {
const struct pci_device_id *id_table; struct device_driver driver;
};
包含了特殊总线区域的定义如下(又用了eepro100驱动);
static struct pci_driver eepro100_driver = { .id_table = eepro100_pci_tbl, .driver = {
.name .bus .probe
= \= &pci_bus_type,
= eepro100_probe,
.remove = eepro100_remove, .suspend = eepro100_suspend, .resume
= eepro100_resume,
},
};
可能发现一些难看的嵌入式结构初始化,甚至有丑陋的位.到目前为止,我们发现这是我们最
24
www.ourkernel.com 我们的内核
想要的方法...
注册
int driver_register(struct device_driver * drv);
在电脑启动时设备注册.对没有特殊总线区域的那些设备,它们将用driver_register 和一个指向device_driver对象结构的指针注册.
但是对于大多数的设备,它们有特定的总线结构,需要通过用一些像 pci_driver_registerr的函数来注册.
设备注册它们的结构越早越好,这非常的重要.在device_driver 对象结构中的许多核心初始化结构的记录包括:参数计数和销.
假设这些区域在任务时间都是有效的,并且也能被设备核心模块或总路线设备使用.
转换总线设备
通过定义封装函数,向新模块的转换更加容易.设备能完全忽略一般的结构.也能让封装的函数填充这些区域.为了回传信号,
总线能定义一般的回转信号,它们能向特定总线发送设备的回传信号.
这种方案被定义为临时的方案.在设备中为了得到class信息,设备无论如何都应该被识别.设备向新模式转换的过程中应该减少结构复杂性 代码字节.推荐在class信息增加时进行转换.
存取
在注册了对象之后,就可以访问像锁和设备链表这样的一般区域了.
int driver_for_each_dev(struct device_driver * drv, void * data,
int (*callback)(struct device * dev, void * data));
设备区域是所有设备一定需要的链表.LDM 的核心为操作所有的设备提供一个求助功能. 这个助手锁住设备在每个节点访问时,做恰当的计数在设备访问时.
25