terrace
驱动牛犊
驱动牛犊
  • 注册日期2004-02-11
  • 最后登录2005-12-04
  • 粉丝0
  • 关注0
  • 积分15分
  • 威望3点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
阅读:5510回复:10

翻译:如何写linux pci设备驱动程序

楼主#
更多 发布于:2004-04-10 18:50
以下是我最近在写一个pci driver时阅读的一篇文章,觉得很有用就翻译出来,希望对大家有所帮助。咳,我当时初学linux driver也是困难重重,向好多人厚颜无耻的缠着问。在此向他们特别是unix1998等高手表示感谢和抱歉。无奈水平有限,可能有些地方译的不对。若大家读时感到不顺畅就看原文。
原文:/Documentation/pci.txt
另外还有一篇很好的关于pci设备驱动程序的文章也共享:
Linux下PCI设备驱动程序开发
http://www-900.ibm.com/developerWorks/cn/linux/l-pci/index.shtml?ca=dwcn-isc&me=ccid


                     如何写linux pci设备驱动程序
        original file:/Documentation/pci.txt            translated by terrace

PCI总线应用领域及其广泛并且令人惊奇。不同的pci设备有不同的需求以及不同的问题。因此,在linux内核中pci层支持就非常重要啦。本文档就是想为驱动程序设计开发人员解决pci处理中的各种问题。

0. Pci设备驱动程序的结构
现在有两种风格的的pci驱动程序结构:新风格的驱动(即让pci层来做大量设备探测
工作并且支持热插拔功能)和旧风格的驱动(即由驱动程序自己探测设备)。除非你有很好的理由,否则就不要用旧风格写驱动程序。当驱动程序找到所驱动的设备后,将执行以下步骤:
启用设备
访问设备配置空间
检测设备资源(如基地址和中断号)
分配设备资源
与设备通讯
   下文将论述以上大部分步骤,其余部分请参考<linux/pci.h>,它有不错的注释。
   如果没有配置pci 子系统(即CONFIG_PCI 没有置位),以下介绍的大部分函数都被定义为内联函数,它们要么是空的,要么返回对应的错误代码以避免在驱动中出现过多的条件宏ifdefs。

1. 新风格的驱动程序
新风格的驱动程序只是在初始化时调用 pci_register_driver,调用时使用一个指向struct pci_driver 的结构指针。该指针包含以下几个分量:
   name           驱动程序名称
 id_table 指向一个与驱动程序相关的设备ID表的指针。大多数驱动程序应当用MODULE_DEVICE_TABLE(pci,…)将该设备ID表导出。在调用prob( )时设成NULL 以让系统检测到所有的pci设备。
probe         指向设备检测函数prob( ) 的指针。该函数将在pci设备ID与设备ID表匹配且还没有被其它驱动程序处理时(一般在对已存在的设备执行pci_register_driver或以后又有新设备插入时)被调用。调用时传入一个指向struct pci_driver结构的指针和与设备匹配的设备ID表做参数。若成功(驱动程序检测到pci设备)则返回0,否则返回一个负的错误代码。这个函数总是在上下文之间调用的,因此可以进入睡眠状态的。
remove        指向一个设备卸载函数remove( )的指针。该函数在pci设备被卸载时(如在注销设备驱动程序或者手动拔出该设备)被调用。同probe一样,该函数也是可以睡眠的。
save_state     在设备被暂停前所保存的设备状态。
suspend       将设备转入低功耗状态而暂停。
sesume        将一个暂停的设备(低功耗状态中)唤醒启动。
enable_wake    允许设备产生唤醒事件以从低功耗状态中恢复。

(请参考 Documentation/power/pci.txt 文件中关于pci电源管理以及相关函数的说明)
ID表是一个 struct pci_device_id 类型的数组,该数组以该类型中每一项都为NULL时结束。struct pci_device_id中有以下几个分量:
vendor,device         设备对应的厂商ID号和设备ID号(或者是PCI_ANY_ID)
subvendor,subdevice    设备对应的子厂商ID号和子设备ID号(或者是PCI_ANY_ID)
class,class_mask       设备对应的类,类掩码表示在比较中采用设备类(class)中的那些位。
driver_data           设备的私有数据。
当一个设备驱动程序存在,只需要调用pci_unregister_driver就可由pci层调用remove( )
函数自动将所驱动的所有设备卸载掉。
请将初始和清理函数前加上以下适当的宏(该宏定义在<linux/init.h>中):
__init        初始化标记,置于驱动程序中初始化函数前。
__exit        结束标记,在非模块化的驱动程序中将被忽略。
__devinit     设备初始化标记。若编译内核时选择了CONFIG_HOTPLUG选项,则等同于__init。
__devexit     同__exit一样。
一般技巧:
    在module_init( )/module_exit( )函数前应标上__init/__exit标记;
  结构体struct pci_driver不要标上以上任何标记;
  ID表数组应当标上 __devinitdata标记;
  probe( )和remove( )函数应当标上__devinit/exit标记;
  如果你确信不是一个支持热插拔的驱动程序,就可只用__init/exit  __initdata/exitdata;
  一个指向标记了 __devexit的函数的指针,一定要由__devexit_p(function_name)来产生。
它会产生对应的函数名,若没有标记了__devexit的函数,则产生NULL。

2. 旧风格的pci设备驱动程序
   旧风格的pci设备驱动程序不会用到pci_register_driver()去探测设备,而需要手动(由驱动程序)使用以下结构去探测设备:
   根据厂商ID和设备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);
   根据厂商ID和设备ID以及子系统厂商ID和设备ID探测设备:
       pci_find_subsys(VENDOR_ID,DEVICE_ID,SUBSYS_VENDOR_ID, SUBSYS_DEVICE_ID, dev);
    你可以使用常量PCI_ANY_ID作为通配符取代VENDOR_ID、DEVICE_ID,这样你就
可以搜索都所有的设备。如果你要以更复杂的条件探测设备,那就得自己遍历所有已知的
pci设备:
       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() 会把PCI_COMMAND
寄存器中的总线主设备允许位置1,并且还会修改延迟计数器中的值。
   若你想使用pci内存写无效事务,调用pci_set_swi() ,它会把PCI_COMMAND寄存
器中的Mem_Wr_Inval位置1,还要确认缓存行长度寄存器(CACHE_LINE_REGISTER)的设置是否正确。要注意检查pci_set_swi()的返回值,并不是所有设备都支持pci内存写无效的。

4. 如何访问pci设备配置空间
必须使用pci_(read|write)_config_(byte|word|dword) 去访问pci设备(由struct pci_dev *
表示)的配置空间。所有以上的函数在成功时返回0,失败时返回错误代码。大多数驱动程序期望在访问有效的pci设备时是不会失败的。
   若你要访问的是pci配置头标准空间,就可以用符号常量来表示寄存器地址以及位设置,这些符号常量定义在<linux/pci.h>中。
   若要访问pci扩展空间能力寄存器,只需要对每个特定的能力调用pci_find_capability()就可以找到相应的寄存器块。
5. 寻址和中断
内存和端口基地址以及中断号不要从pci配置空间中读出而要使用pci_dev结构体中相
应的值,因为它们可能被映射到内核中。关于如何访问设备内存,请参阅Documentation/IO-mapping.txt文件。还需要调用request_region()函数和request_mem_region()分别申请IO地址和设备内存地址范围以确保没有其它驱动使用该设备。
   所有的中断处理程序要使用共享中断号,还可以使用参数devid将中断号IRQs映射为设备数据结构。
6. 其它有用的函数
   pci_find_slot()      根据设备所在的总线号和插槽号,探测相应的pci设备
   pci_set_power_state() 设置pci电源管理状态
   pci_find_capability()  在设备的能力表中找出指定的能力
   pci_module_init()     内联函数,确保驱动程序正确的初始化以及错误处理
   pci_resource_start()   返回pci地址范围的总线起始地址
   pci_resource_end()    返回pci地址范围的总线终止地址
   pci_resource_len()    返回pci地址范围的长度,以字节为单位
   pci_set_drvdata()     为pci_dev设置私有数据指针
   pci_get_drvdata()     从pci_dev结构中得到其私有数据指针
   pci_set_mwi()        启用设备内存写无效事务
   pci_clear_mwi()      禁用设备内存写无效事务
7. 其它提示
当向用户显示pci插槽时(如驱动程序想告诉用户它所找到的设备卡),可用
pci_dev->slot_name。
   总是使用一个指向struct pci_dev的指针来引用某个pci设备。所有的pci层接口函数都使用该指针。不要使用总线/插槽号/功能号,它们有特别的用途――有多个主总线的系统说起来是很复杂的。
8. 已弃用的函数
以下的接口函数只是为了向上兼容而暂时保留,请不要在你的新式驱动程序中使用它们:
pcibios_present()          以前在使用,现在你不必测试pci子系统是否存在。若不存在pci子系统,pci 设备表是空的,并且所有的探测设备函数都将返回NULL。
pcibios_(read|write)_*      现已被相应的pci_(read|write)_*取代
pcibios_find_*            现已被相应的pci_find_*取代

最新喜欢:

milkywaymilkyw...
younger521
驱动牛犊
驱动牛犊
  • 注册日期2004-04-24
  • 最后登录2004-05-20
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2004-05-15 19:50
请问terrace
/Documentation/pci.txt在哪里能找到呢?我最近在学PCI驱动开发。谢谢哈
Tom.Cat
禁止发言
禁止发言
  • 注册日期2001-10-10
  • 最后登录2019-07-29
  • 粉丝1
  • 关注0
  • 积分-53792分
  • 威望197411点
  • 贡献值0点
  • 好评度5点
  • 原创分0分
  • 专家分0分
  • 社区居民
板凳#
发布于:2004-05-17 11:03
用户被禁言,该主题自动屏蔽!
younger521
驱动牛犊
驱动牛犊
  • 注册日期2004-04-24
  • 最后登录2004-05-20
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
地板#
发布于:2004-05-20 08:43
恩  是哈。英文原文啊。
Cary_you
驱动牛犊
驱动牛犊
  • 注册日期2003-01-14
  • 最后登录2007-01-23
  • 粉丝0
  • 关注0
  • 积分12分
  • 威望2点
  • 贡献值0点
  • 好评度1点
  • 原创分0分
  • 专家分0分
地下室#
发布于:2004-05-23 18:05
在内核里
highwaylost
驱动小牛
驱动小牛
  • 注册日期2003-12-31
  • 最后登录2005-09-26
  • 粉丝0
  • 关注0
  • 积分1分
  • 威望1点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
5楼#
发布于:2004-05-24 09:02
翻译的很好了
“恨怨悲苦憎怒噌 仁爱慈孝耻义廉 是故恨人所以得仁 无爱者必不怨 不慈者必无悲 孝而有苦 憎后耻来 义自怒生 廉人心噌 夹天地七大苦 破人情七大碍--”
lh1571
驱动牛犊
驱动牛犊
  • 注册日期2005-07-31
  • 最后登录2005-08-02
  • 粉丝0
  • 关注0
  • 积分1分
  • 威望2点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
6楼#
发布于:2005-08-02 14:09
"......调用时传入一个指向struct pci_driver结构的指针和与设备匹配的设备ID表做参数...."
not pci_driver, is pci_dev structure representing the device
alto618
驱动牛犊
驱动牛犊
  • 注册日期2005-02-10
  • 最后登录2008-04-12
  • 粉丝0
  • 关注0
  • 积分120分
  • 威望12点
  • 贡献值0点
  • 好评度12点
  • 原创分0分
  • 专家分0分
7楼#
发布于:2007-03-19 17:00
基本没看懂,会努力的!
chipofchina
驱动牛犊
驱动牛犊
  • 注册日期2007-12-20
  • 最后登录2009-03-02
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望3点
  • 贡献值0点
  • 好评度2点
  • 原创分0分
  • 专家分0分
8楼#
发布于:2008-02-29 14:51
写的很好,挺不错的。
xaxiao
驱动小牛
驱动小牛
  • 注册日期2007-09-11
  • 最后登录2010-02-10
  • 粉丝1
  • 关注0
  • 积分1分
  • 威望199点
  • 贡献值0点
  • 好评度197点
  • 原创分2分
  • 专家分0分
9楼#
发布于:2008-03-30 09:21
不错
loveYUE
驱动牛犊
驱动牛犊
  • 注册日期2009-06-18
  • 最后登录2011-03-01
  • 粉丝0
  • 关注0
  • 积分1分
  • 威望11点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
10楼#
发布于:2009-06-18 09:37
写得不错
游客

返回顶部