Linux下PCI板卡驱动程序的编写

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

原文: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

 
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux下,通常可以使用驱动程序包(如Intel的iwlwifi-firmware和Broadcom的wl)来安装网卡设备的驱动程序。另外,也可以使用发行版提供的更新机制(如Ubuntu的apt-get)来自动安装驱动程序。 ### 回答2: 在Linux操作系统下,网卡设备驱动程序是连接计算机和网络之间的关键组件,它负责控制和管理网卡设备的各种功能和操作。 在Linux中,网卡设备驱动程序是作为内核模块加载的。当系统启动时,内核会根据硬件检测到的网卡设备信息加载相应的驱动程序。这些驱动程序将与内核进行交互,以提供网络通信能力。 网卡设备驱动程序包含了相应网卡设备的底层控制逻辑和操作指令。它通过与内核交互,将上层网络协议栈的请求转换为底层网卡设备可以理解和处理的操作。这意味着网卡设备驱动程序负责处理包括数据发送、接收、处理和中断等操作,使得网络通信能够在硬件层面得以实现。 在Linux系统中,网卡设备驱动程序负责管理和配置网卡设备的属性和参数。用户可以通过不同的工具和命令来进行网卡设备的管理,如ifconfig、ethtool等。这些工具可以帮助用户查看和设置网卡设备的IP地址、子网掩码、数据帧大小等属性,以满足用户的网络需求。 此外,通过更新和升级网卡设备驱动程序,用户可以得到更好的性能和更好的兼容性。开源的Linux系统为用户提供了众多网卡设备驱动程序的选择,以满足不同硬件设备的需求。 综上所述,Linux下的网卡设备驱动程序承担着控制和管理网卡设备的重要角色,是实现网络通信的关键组件。通过加载适当的驱动程序,用户可以使用各种网卡设备并享受网络通信带来的便利。 ### 回答3: 在Linux系统中,网卡设备驱动程序负责管理和控制计算机上的网络接口卡。它们允许操作系统与网络硬件进行通信,并实现网络数据的传输。 网卡设备驱动程序通常由硬件制造商开发,以确保其与特定的网卡设备兼容。这些驱动程序编写成模块形式,可以动态地加载到内核中,或者作为静态链接编译到内核中。 在加载驱动程序时,操作系统通过设备树(如果使用的是现代的设备模型)或基于PCI系统的ioctl调用与网卡设备进行通信。驱动程序初始化网卡设备,设置接口参数,例如MAC地址,MTU等。此外,驱动程序还为设备提供一组网络协议参数,如IP地址、子网掩码和默认网关等。 一旦网卡设备驱动程序加载成功,操作系统便可以通过网络协议栈来发送和接收数据包。驱动程序负责处理硬件中断,收集和发送数据以及管理网络设备的状态。 在Linux中,常见的网卡设备驱动程序是以太网驱动程序。这些驱动程序包括广泛的硬件支持,从常见的千兆以太网控制器到无线网卡设备。 总之,Linux下的网卡设备驱动程序是允许操作系统与网络硬件进行通信,并实现网络数据传输的关键组件。它们负责初始化和管理网卡设备,处理硬件中断以及提供网络协议参数。这些驱动程序在操作系统启动时加载,以确保计算机可以连接到网络并进行通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值