2.7 驱动模型编程模式
由于Linux驱动模型核心的支持,驱动模型编程变得比较简单,并且有一定的“套路”可循,以及后面要讲述的PCI子系统和SCSI子系统都是驱动模型编程的范例,我们这里先大致概括如下。
1.总线类型逻辑
定义一个总线类型(struct bus_type)对象,在该对象中,需要指定总线类型的名字,还需要实现一些回调函数,例如match、probe等。
在子系统或模块加载时调用bus_register函数向Linux内核注册这个总线类型,在子系统或模块卸载时调用bus_unregister函数从Linux内核注销这个总线类型。一般子系统或模块也可以根据需要对bus_register和bus_unregister函数进行封装。
如果所有功能不是在一个模块中实现,就需要使用EXPORT_SYMBOL宏导出总线类型以便其他代码使用。
2.设备逻辑
定义功能设备结构体,通常将驱动模型设备描述符(struct device)作为一个域嵌入到这个结构体。此外,还可以在结构体中嵌入另一个驱动模型设备描述符(struct device)。如果是这样,那么前一个我们称为“内嵌的通用设备”,它是为了将这个功能设备链入到总线类型的设备链表而用的,而后一个我们称为“内嵌的类设备”,它是为了将这个功能设备链入到类的设备链表而用的。建议定义专门的宏或内联函数实现从驱动模型设备获得功能设备,这种定义方式可以让编译器检查要执行的操作的类型安全性。
在功能设备被发现时,除了业务逻辑所必须的初始化外,还需要对“内嵌的通用设备”以及“内嵌的类设备”进行初始化。对于“内嵌的通用设备”,最重要的要初始化的域有bus_id、parent和bus。bus_id域是一个ASCII字符串,包含了设备在总线上的地址,其格式取决于特定的总线,这一点在sysfs中表示设备非常必要。parent是设备物理意义上的父亲。总线驱动需要正确设置这个域。bus域是一个指向设备所属总线类型的指针,这个域应该设置为前面声明并初始化的bus_type。对于“内嵌的类设备”,最重要的要初始化的域有class。当然,可以调用device_initialize函数对它们执行一些基本的初始化动作。
在其后的某个时间,应该调用device_add函数,分别传入“内嵌的通用设备”和“内嵌的类设备”的地址,向Linux内核添加功能设备。这将会把功能设备同时链入到总线类型的设备链表,以及类的设备链表中,尽管实际上的功能设备对象只有一个。它还会在sysfs文件系统中为功能设备创建相应的目录,并添加属性文件以及符号链接文件等。如果要从Linux内核中删除功能设备,则应该分别针对“内嵌的通用设备”和“内嵌的类设备”调用device_del函数。
功能设备被添加到系统,它就确确实实存在了。至于是何时被绑定到功能驱动,那就是驱动模型核心的事了。
3.驱动逻辑
驱动逻辑和设备逻辑类似。首先是声明一个功能驱动结构体,将驱动模型驱动描述符(struct device_driver)作为它的一个域,还可以根据需要包含了驱动模型核心可能调用的操作集。
在稍后的某个时刻,调用driver_register函数向Linux内核注册功能驱动,以其内嵌的驱动模型驱动描述符为参数,在注册之前,必须初始化一些必要的域:例如name和bus。其中bus域必须指向前面声明并初始化的bus_type。与此相反,在驱动从Linux内核注销时,调用driver_unregister函数。一般子系统或模块也可以根据需要对driver_register和driver_unregister函数进行封装。
注册驱动会将功能驱动挂入总线类型的驱动链表,并在sysfs文件系统中为功能驱动创建对应的目录,以及属性文件和符号链接等。与此同时,驱动模型会尝试将驱动去绑定总线类型的设备链表中的设备。是否可以绑定完全是功能特定的,因此往往最终要调用功能驱动中定义的回调方法。
4.类和接口逻辑
取决于具体的功能,子系统或模块也可使实现类和接口逻辑。它们基本上也采用类似的流程,具体例子可以参考SCSI子系统,以及sg模块。