linux设备驱动(十八)–设备驱动模型(三)各环节整合

PCI设备创建过程

这部分分成两部分学习,一是参考前辈的文章,分析lddbus和sculld两部分源码,二是参考ldd3学习pci部分

第一部分ldd_bus声明了一个bus_type结构的ldd_bus_type:

struct bus_type ldd_bus_type = {

.name = “ldd”,

.match = ldd_match,

.uevent = ldd_uevent,

};

将ldd_bus_type添加到内核和从内核卸载的代码如下:

static int _init ldd_bus_init(void)

{

int ret;

ret =bus_register(&ldd_bus_type);/*注册总线,在调用这个函数之后ldd_bus_type 结构体将向内核注册,在/sys/bus中出现ldd文件夹,其中包含两个目录:devices 和drivers */

if(ret)

return ret;

if(bus_create_file(&ldd_bus_type,&bus_attribute_version)) /*添加总线属性,添加成功后会在/sys/bus/ldd中出现version属性文件

printk(KERN_NOTICE “unable to create this attribute/n”);

ret = device_register(&ldd_bus);/*将总线作为设备注册,因为处理器的外围各种控制器相对于arm核心来说,都可以看成是一种外设,注册成功之后在sys/device目录下出现ldd0目录。

if(ret)

printk(KERN_NOTICE “unable to register ldd/n”);

printk(KERN_NOTICE “Mount lddbus ok !/nBus device is ldd0 !/nYou can see me in sys/module/ , sys/devices/ and sys/bus/ ! /n”);

return ret;

}

static void ldd_bus_exit(void)

{

device_unregister(&ldd_bus);

bus_unregister(&ldd_bus_type);

}

module_init(ldd_bus_init);

module_exit(ldd_bus_exit);

lddbus模块的主要部分就是这些,很简单。因为这只不过是一个虚拟的总线,没有实际的驱动。模块还导出了加载总线设备和总线驱动时需要用到的注册和注销函数。对于实际的总线,应该还要导出总线的读写例程。

将总线设备和驱动注册函数放在lddbus模块,并导出给其他的总线驱动程序使用,是因为注册总线设备和驱动需要总线结构体的信息,而且这些注册函数对于所有总线设备和驱动都一样。只要这个总线驱动一加载,其他的总线驱动程序就可以通过调用这些函数注册总线设备和驱动,方便了总线设备驱动的作者,减少了代码的冗余。

这些注册函数内部调用driver_register、device_register 和 driver_unregister、device_unregister 这些函数。

二、sculld模块:在scull的基础上添加设备和驱动注册和注销函数。

//*******在源码的声明阶段添加如下代码,以增加设备和驱动的结构体*****struct sculld_dev *sculld_devices;/* allocated in scull_init_module */

/* Device model stuff */static struct ldd_driver sculld_driver = {.version = "$Revision: 1.21-tekkamanninja $",.module = THIS_MODULE,.driver = { //在内部嵌入一个driver.name = "sculld",},};//**************************************************************//******增加设备注册函数和设备号属性********************************static ssize_t sculld_show_dev(struct device *ddev, struct device_attribute *attr , char *buf){struct sculld_dev *dev = ddev->driver_data;return print_dev_t(buf, dev->cdev.dev);}static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);static void sculld_register_dev(struct sculld_dev *dev, int index){sprintf(dev->devname, "sculld%d", index);dev->ldev.name = dev->devname;dev->ldev.driver = &sculld_driver;dev->ldev.dev.driver_data = dev;register_ldd_device(&dev->ldev);if (device_create_file(&dev->ldev.dev, &dev_attr_dev)) printk( "Unable to create dev attribute ! /n");}//*****************************************************************/*还要在模块的初始化函数和模块清除函数中添加设备和驱动的注册和注销函数*/sculld_register_dev(sculld_devices + i, i);register_ldd_driver(&sculld_driver);unregister_ldd_device(&sculld_devices[i].ldev);unregister_ldd_driver(&sculld_driver);

修改后好模块就可以实现向sysfs文件系统导出信息。

三、分析设备和驱动注册和注销核心函数,了解一般的注册、注销过程。

(1)设备的注册

在驱动程序中对设备进行注册的核心函数是:

int device_register(struct device *dev){device_initialize(dev);return device_add(dev);}

在 device_register 函数中, 驱动核心初始化 device 结构体中的许多成员, 向 kobject 核心注册设备的 kobject ( 导致热插拔事件产生,当设备核心添加或删除一个kobject时都会产生热插拔事件), 接着添加设备到其 parent 节点所拥有的设备链表中。此后所有的设备都可通过正确的顺序被访问, 并知道其位于设备层次中的哪一点。

设备接着被添加到总线相关的设备链表(包含了所有向总线注册的设备)中。接着驱动核心遍历这个链表, 为每个驱动程序调用该总线的match函数。

match函数主要是将驱动核心传递给它的 struct device 和 struct device_driver转换为特定的设备、驱动结构体 ,检查设备的特定信息, 以确定驱动程序是否支持该设备:

若不支持, 函数返回 0 给驱动核心,这样驱动核心移向链表中的下一个驱动;

若支持, 函数返回 1 给驱动核心,使驱动核心设置struct device 中的 driver 指针指向这个驱动, 并调用在 struct device_driver 中指定的 probe 函数.

probe 函数(又一次) 将驱动核心传递给它的 struct device 和 struct device_driver转换为特定的设备、驱动结构体 ,并再次验证这个驱动是否支持这个设备, 递增设备的引用计数, 接着调用总线驱动的 probe 函数:

若总线 probe 函数认为它不能处理这个设备,则返回一个负的错误值给驱动核心,这样驱动核心移向链表中的下一个设备;

若这个 probe 函数能够处理这个设备, 则初始化这个设备, 并返回 0 给驱动核心。这会使驱动核心添加设备到与这个特定驱动所绑定的设备链表中, 并在 /sys/bus的总线目录中的 drivers 目录中创建一个到这个设备符号链接(指向/sys/devices中的设备),使用户准确知道哪个驱动被绑定到了哪个设备。

(2)设备的注销 在驱动程序中对设备进行注销的核心函数是:

void device_unregister(struct device * dev)

在 device_unregister 函数中, 驱动核心将删除这个设备的驱动程序(如果有)指向这个设备的符号链接, 并从它的内部设备链表中删除该设备, 再以 device 结构中的 struct kobject 指针为参数,调用 kobject_del。kobject_del 函数引起用户空间的 hotplug 调用,表明 kobject 现在从系统中删除, 接着删除所有该 kobject 以前创建的、与之相关联的 sysfs 文件和目录。kobject_del 函数也去除设备自身的 kobject 引用。此后, 所有的和这个设备关联的 sysfs 入口被去除, 并且和这个设备关联的内存被释放。

(3)驱动程序的注册

在驱动程序中对驱动程序进行注册的核心函数是:

int driver_register(struct device_driver * drv)

driver_register 函数初始化 struct device_driver 结构体(包括 一个设备链表及其增删对象函数 和 一个自旋锁), 然后调用 bus_add_driver 函数。

bus_add_driver进行如下操作:

(1)查找驱动关联的总线:若未找到, 立刻返回负的错误值;(2)根据驱动的名字和关联的总线,创建驱动的 sysfs 目录;(3)获取总线的内部锁, 遍历所有的已经注册到总线的设备,为这些设备调用match函数, 若成功,进行剩下的绑定过程。(类似注册设备,不再赘述)

(4)驱动程序的注销

删除驱动程序是一个简单的过程,在驱动程序中对驱动程序进行注销的核心函数是:

void driver_unregister(struct device_driver * drv)

deiver_unregister 函数通过清理在 sysfs 树中连接到这个驱动入口的 sysfs 属性,来完成一些基本的管理工作。然后遍历所有属于该驱动的设备,为其调用 release 函数(类似设备从系统中删除时调用 release 函数)。

在所有的设备与驱动程序脱离后,通常在驱动程序中会使用下面两个函数:

down(&drv->unload_sem);up(&drv->unload_sem);

它们在函数返回给调用者之前完成。这样做是因为在安全返回前,代码需要等待所有的对这个驱动的引用计数为 0。模块卸载时,通常都要调用 driver_unregister 函数作为退出的方法。 只要驱动程序被设备引用并且等待这个锁时,模块就需要保留在内存中。这使得内核知道何时可以安全从内存删除驱动。

也只有懂的接受自己的失败,才能更好的去发挥自身优势,也才能够更好的去实现自我;

linux设备驱动(十八)–设备驱动模型(三)各环节整合

相关文章:

你感兴趣的文章:

标签云: