--------------------------------------------------------------------------------

原文:How To Write Linux PCI Drivers
作者:Martin Mares <mj@ucw.cz> on 07-Feb-2000
位置:Documentation/pci.txt
编译:dqzhangp@263.net

0. PCI驱动程序的架构
有两种类型的PCI驱动程序,新型的驱动程序,它把多数的设备探测工作留给了PCI层, and support online insertion and removal of devices [thus supporting PCI, hot-pluggable PCI and CardBus in single driver])。还有一种PCI驱动程序,他自己完成所有的探测工作。

当驱动程序发现相应的设备,需要完成下面的功能:

激活设备
对设备配置空间进行存取
发现资源,如地址、中断号等
分配这些资源
与设备进行通讯
1. 新型驱动程序
新型的设备驱动程序在初始化的时候调用函数pci_register_driver,此函数的参数是一个struct pci_driver类型的指针。struct pci_driver包括以下一些项:

name:驱动的名字
id_table:指向驱动程序感兴趣的设备的设备ID列表。很多设备驱动程序需要使用 MODULE_DEVICE_TABLE(pci,...)输出此表。如果设为NULL则感兴趣的设备是系统识别的所有设备。
probe:一个探测函数的指针,用来探测所有与ID表中匹配的设备。This function gets passed a pointer to the pci_dev structure representing the device and also which entry in the ID table did the device match。驱动程序接受设备的时候返回0,否则返回一个错误代码。This function al4矗绲刂贰⒅卸虾诺�
分配这些资源
与设备进行通讯
1. 新型驱动程序
新型的设备驱动程序在初始化的时候调用函数pci_register_driver,此函数的参数是一个struct pci_driver类型的指针。struct pci_driver包括以下一些项:

name:驱动的名字
id_table:指向驱动程序感兴趣的设备的设备ID列表。很多设备驱动程序需要使用 MODULE_DEVICE_TABLE(pci,...)输出此表。如果设为NULL则感兴趣的设备是系统识别的所有设备。
probe:一个探测函数的指针,用来探测所有与ID表中匹配的设备。This function gets passed a pointer to the pci_dev structure representing the device and also which entry in the ID table did the device match。驱动程序接受设备的时候返回0,否则返回一个错误代码。This function always gets called from process context, so it can sleep.
remove:一个函数指针,当一个驱动程序所驱动的一个设备被卸掉的时候被调用。This function always gets called from process context, so it can sleep.
save_state:在挂起前存储设备状态。
suspend 把设备置于低电状态。
resume 把设备从低电状态唤醒。
enable_wake 是设备能够从低电状态产生唤醒事件。
ID列表是一个以全零入口结尾的struct pci_device_id类型的数组。struct pci_device_id:

vendor, device Vendor and device ID to match (or PCI_ANY_ID)
subvendor, Subsystem vendor and device ID to match (or PCI_ANY_ID) subdevice
class, Device class to match. The class_mask tells which bits
class_mask of the class are honored during the comparison.
driver_data Data private to the driver.
驱动程序退出的时候调用函数pci_unregister_driver(),PCI层自动调用remove钩子函数。请把相应的函数设�%Aways gets called from process context, so it can sleep.
remove:一个函数指针,当一个驱动程序所驱动的一个设备被卸掉的时候被调用。This function always gets called from process context, so it can sleep.
save_state:在挂起前存储设备状态。
suspend 把设备置于低电状态。
resume 把设备从低电状态唤醒。
enable_wake 是设备能够从低电状态产生唤醒事件。
ID列表是一个以全零入口结尾的struct pci_device_id类型的数组。struct pci_device_id:

vendor, device Vendor and device ID to match (or PCI_ANY_ID)
subvendor, Subsystem vendor and device ID to match (or PCI_ANY_ID) subdevice
class, Device class to match. The class_mask tells which bits
class_mask of the class are honored during the comparison.
driver_data Data private to the driver.
驱动程序退出的时候调用函数pci_unregister_driver(),PCI层自动调用remove钩子函数。请把相应的函数设定为初始化或者清除类型。相应的宏在头文件include/linux/init.h中定义:

__init:初始化代码。驱动程序初始化完成后从内存中清除。
__exit:Exit code。如果此驱动不是模块的方式,则代码被忽略。
__devinit:Device initialization code. 如果内核没有被配置为CONFIG_HOTPLUG,即不支持热插拔,则与__init相同,否则就是一个普通的函数。
__devexit:与 __exit一样。
技巧:

函数module_init()和module_exit() 应当分别标记为 __init和exit。
结构pci_driver不能标记为__init和exit。
ID table array 应当标记为 __devinitdata。
函数probe() 和 remove() 应当标记为 __devinit 和 exit。
If you are sure the driver is not a hotplug driver then use only __init/exit __initdata/exitdata.
Pointers to functions marked as __devexit must be created using __devexit_p(function_name). That will generat8为初始化或者清除类型。相应的宏在头文件include/linux/init.h中定义:

__init:初始化代码。驱动程序初始化完成后从内存中清除。
__exit:Exit code。如果此驱动不是模块的方式,则代码被忽略。
__devinit:Device initialization code. 如果内核没有被配置为CONFIG_HOTPLUG,即不支持热插拔,则与__init相同,否则就是一个普通的函数。
__devexit:与 __exit一样。
技巧:

函数module_init()和module_exit() 应当分别标记为 __init和exit。
结构pci_driver不能标记为__init和exit。
ID table array 应当标记为 __devinitdata。
函数probe() 和 remove() 应当标记为 __devinit 和 exit。
If you are sure the driver is not a hotplug driver then use only __init/exit __initdata/exitdata.
Pointers to functions marked as __devexit must be created using __devexit_p(function_name). That will generate the function name or NULL if the __devexit function will be discarded.
2. 如何手动发现PCI设备(老的方式)
PCI驱动程序不使用函数pci_register_driver()查找PCI设备,可以使用下面的方法:

通过生产商或设备ID号:
struct pci_dev *dev = NULL;
while (dev = pci_find_device(VENDOR_ID, DEVICE_ID, dev))
configure_device(dev);

通过种类ID号搜索:
pci_find_class(CLASS_ID, dev)

Searching by both vendor/device and subsystem vendor/device ID:
pci_find_subsys(VENDOR_ID, DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev).

利用常数PCI_ANY_ID替代VENDOR_ID或者DEVICE_ID,这种方法用来搜索特定厂商的所有设备。
有时候需要根据比较复杂的规则决定要查找的设备,这可以通过遍历发现的所有设备,根据每个设备的具体情况作出选择。
struct pci_dev *dev;
pci_for_each_dev(dev) {
... do ane the function name or NULL if the __devexit function will be discarded.
2. 如何手动发现PCI设备(老的方式)
PCI驱动程序不使用函数pci_register_driver()查找PCI设备,可以使用下面的方法:

通过生产商或设备ID号:
struct pci_dev *dev = NULL;
while (dev = pci_find_device(VENDOR_ID, DEVICE_ID, dev))
configure_device(dev);

通过种类ID号搜索:
pci_find_class(CLASS_ID, dev)

Searching by both vendor/device and subsystem vendor/device ID:
pci_find_subsys(VENDOR_ID, DEVICE_ID, SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev).

利用常数PCI_ANY_ID替代VENDOR_ID或者DEVICE_ID,这种方法用来搜索特定厂商的所有设备。
有时候需要根据比较复杂的规则决定要查找的设备,这可以通过遍历发现的所有设备,根据每个设备的具体情况作出选择。
struct pci_dev *dev;
pci_for_each_dev(dev) {
... do anything you want with dev ...
}

为了与旧的内核的设备排序兼容,可以使用函数pci_for_each_dev_reverse(dev)以反方向遍历设备列表。
3. 激活设备
在使用发现的设备之前,必须调用函数pci_enable_device()激活它,该函数使得设备的I/O口和存储区可用,在必要时分派丢失的资源,或者用来唤醒处于挂起状态的设备。

调用函数pci_set_master()使得设备处于bus mastering模式,此函数设置PCI_COMMAND寄存器的bus master位。同时 fixes the latency timer value if it's set to something bogus by the BIOS。所谓bus mastering模式,就是直接与总线上的其他设备通讯, 而不必经过CPU,又叫做first-party DMA。

如果要使用PCI的Memory-Write-Invalidate transaction,调用函数pci_set_mwi()。This enables bit PCI_COMMAND bit for Mem-Wr-Inval and also ensures thatything you want with dev ...
}

为了与旧的内核的设备排序兼容,可以使用函数pci_for_each_dev_reverse(dev)以反方向遍历设备列表。
3. 激活设备
在使用发现的设备之前,必须调用函数pci_enable_device()激活它,该函数使得设备的I/O口和存储区可用,在必要时分派丢失的资源,或者用来唤醒处于挂起状态的设备。

调用函数pci_set_master()使得设备处于bus mastering模式,此函数设置PCI_COMMAND寄存器的bus master位。同时 fixes the latency timer value if it's set to something bogus by the BIOS。所谓bus mastering模式,就是直接与总线上的其他设备通讯, 而不必经过CPU,又叫做first-party DMA。

如果要使用PCI的Memory-Write-Invalidate transaction,调用函数pci_set_mwi()。This enables bit PCI_COMMAND bit for Mem-Wr-Inval and also ensures that the cache line size register is set correctly. 一定要检查函数pci_set_mwi()的返回值,因为不是所有的架构都支持Memory-Write-Invalidate。

4. 如何访问PCI设备的配置空间
可以使用函数pci_(read|write)_config_(byte|word|dword)访问一个设备的配置空间,此设备用struct pci_dev * 来表示。所有这些函数调用成功是的返回值都为0,如果失败则返回一个错误代码:PCIBIOS_... ,这个错误代码可以利用函数pcibios_strerror转换成一个字符串。Most drivers expect that accesses to valid PCI devices don't fail.

If you access fields in the standard portion of the config header, please use symbolic names of locations and bits declared in <linux/pci.h>.

If you need to access Extended PCI Capability registers, just call pci_find_capability() for the particular capability and it will find the corresponding register block for you. 

5.地址空间与中断
Memory and port addresses and interrupt numbers should NOT the cache line size register is set correctly. 一定要检查函数pci_set_mwi()的返回值,因为不是所有的架构都支持Memory-Write-Invalidate。

4. 如何访问PCI设备的配置空间
可以使用函数pci_(read|write)_config_(byte|word|dword)访问一个设备的配置空间,此设备用struct pci_dev * 来表示。所有这些函数调用成功是的返回值都为0,如果失败则返回一个错误代码:PCIBIOS_... ,这个错误代码可以利用函数pcibios_strerror转换成一个字符串。Most drivers expect that accesses to valid PCI devices don't fail.

If you access fields in the standard portion of the config header, please use symbolic names of locations and bits declared in <linux/pci.h>.

If you need to access Extended PCI Capability registers, just call pci_find_capability() for the particular capability and it will find the corresponding register block for you. 

5.地址空间与中断
Memory and port addresses and interrupt numbers should NOT be read from the config space. You should use the values in the pci_dev structure as they might have been remapped by the kernel.

See Documentation/IO-mapping.txt for how to access device memory.

You still need to call request_region() for I/O regions and request_mem_region() for memory regions to make sure nobody else is using the same device.

All interrupt handlers should be registered with SA_SHIRQ and use the devid to map IRQs to devices (remember that all PCI interrupts are shared). 

6.其它相关的函数
pci_find_slot() Find pci_dev corresponding to given bus and slot numbers.

pci_set_power_state() Set PCI Power Management state (0=D0 ... 3=D3)

pci_find_capability() Find specified capability in device's capability list.

pci_module_init() Inline helper function for ensuring correct

pci_driver initialization and error handling.

pci_resource_start() Returns bus start address for a given PCI region

pci_resource_end() Returns bus end address for a given PCI region

pci_resource_len() Returns the byte length of a PCI region

pci_set_drvdata() Set private driver data pointer for a pci_dev

pci_get_drvdata() Return private driver data pointer for a pci_dev

pci_set_mwi() Enable Memory-Write-Invbe read from the config space. You should use the values in the pci_dev structure as they might have been remapped by the kernel.

See Documentation/IO-mapping.txt for how to access device memory.

You still need to call request_region() for I/O regions and request_mem_region() for memory regions to make sure nobody else is using the same device.

All interrupt handlers should be registered with SA_SHIRQ and use the devid to map IRQs to devices (remember that all PCI interrupts are shared). 

6.其它相关的函数
pci_find_slot() Find pci_dev corresponding to given bus and slot numbers.

pci_set_power_state() Set PCI Power Management state (0=D0 ... 3=D3)

pci_find_capability() Find specified capability in device's capability list.

pci_module_init() Inline helper function for ensuring correct

pci_driver initialization and error handling.

pci_resource_start() Returns bus start address for a given PCI region

pci_resource_end() Returns bus end address for a given PCI region

pci_resource_len() Returns the byte length of a PCI region

pci_set_drvdata() Set private driver data pointer for a pci_dev

pci_get_drvdata() Return private driver data pointer for a pci_dev

pci_set_mwi() Enable Memory-Write-Invalidate transactions.

pci_clear_mwi() Disable Memory-Write-Invalidate transactions.

7. Miscellaneous hints
When displaying PCI slot names to the user (for example when a driver wants to tell the user what card has it found), please use pci_dev->slot_name

for this purpose. Always refer to the PCI devices by a pointer to the pci_dev structure.

All PCI layer functions use this identification and it's the only reasonable one. Don't use bus/slot/function numbers except for very special purposes -- on systems with multiple primary buses their semantics can be pretty complex.

If you're going to use PCI bus mastering DMA, take a look at Documentation/DMA-mapping.txt. 

参考资料
linux/include/linux/pci.h
linux/Documentation/power/pci.txt
对ISA总线DMA的实现
linux/Documentation/DMA-mapping.txt

 
Logo

更多推荐