⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ch12.html

📁 Linux设备驱动经典
💻 HTML
📖 第 1 页 / 共 4 页
字号:
 { 0, }, }; </pre><p>这些例子创建一个 struct pci_device_id 结构的列表, 列表中最后一个是被设置为全零的的空结构. 这个 ID 的数组用在 struct pci_driver (下面讲述), 并且它还用来告诉用户空间这个特定的驱动支持哪个设备.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="MODULEDEVICETABLE.sect2"></a>12.1.4.&#160;MODULEDEVICETABLE 宏</h3></div></div></div><p>这个 pci_device_id 结构需要被输出到用户空间, 来允许热插拔和模块加载系统知道什么模块使用什么硬件设备. 宏 MODULE_DEVICE_TABLE 完成这个. 例如:</p><pre class="programlisting">MODULE_DEVICE_TABLE(pci, i810_ids); </pre><p>这个语句创建一个局部变量称为 __mod_pci_device_table, 它指向 struct pci_device_id 的列表. 稍后在内核建立过程中, depmod 程序在所有的模块中寻找 __mod_pci_device_table. 如果找到这个符号, 它将数据拉出模块并且添加到文件 /lib/modules/KERNEL_VERSION/modules.pcimap. 在 depmod 完成后, 所有的被内核中的模块支持的 PCI 设备被列出, 带有它们的模块名子, 在那个文件中. 当内核告知热插拔系统有新的 PCI 设备已找到, 热插拔系统使用 moudles.pcimap 文件来找到正确的驱动来加载.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="RegisteringaPCIDriver.sect2"></a>12.1.5.&#160;注册一个 PCI 驱动</h3></div></div></div><p>为了被正确注册到内核, 所有的 PCI 驱动必须创建的主结构是 struct pci_driver 结构. 这个结构包含许多函数回调和变量, 来描述 PCI 驱动给 PCI 核心. 这里是这个结构的一个 PCI 驱动需要知道的成员:</p><div class="variablelist"><dl><dt><span class="term"><span>const char *name;</span></span></dt><dd><p>驱动的名子. 它必须是唯一的, 在内核中所有 PCI 驱动里面. 通常被设置为和驱动模块名子相同的名子. 它显示在 sysfs 中在 /sys/bus/pci/drivers/ 下, 当驱动在内核时.</p></dd><dt><span class="term"><span>const struct pci_device_id *id_table;</span></span></dt><dd><p>指向 struct pci_device_id 表的指针, 在本章后面描述它.</p></dd><dt><span class="term"><span>int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);</span></span></dt><dd><p>指向 PCI 驱动中 probe 函数的指针. 这个函数被 PCI 核心调用, 当它有一个它认为这个驱动想控制的 struct pci_dev 时. 一个指向 struct pci_device_id 的指针, PCI 核心用来做这个决定的, 也被传递给这个函数. 如果这个 PCI 驱动需要这个传递给它的 struct pci_dev, 它应当正确初始化这个设备并且返回 0. 如果这个驱动不想拥有这个设备, 或者产生一个错误, 它应当返回一个负的错误值. 关于这个函数的更多的细节在本章后面. </p></dd><dt><span class="term"><span>void (*remove) (struct pci_dev *dev);</span></span></dt><dd><p>指向 PCI 核心在 struct pci_dev 被从系统中去除时调用的函数的指针, 或者当 PCI 驱动被从内核中卸载时. 关于这个函数的更多的细节在本章后面. </p></dd><dt><span class="term"><span>int (*suspend) (struct pci_dev *dev, u32 state);</span></span></dt><dd><p>当 struct pci_dev 被挂起时 PCI 核心调用的函数的指针. 挂起状态在 state 变量里传递. 这个函数是可选的; 一个驱动不必提供它.</p></dd><dt><span class="term"><span>int (*resume) (struct pci_dev *dev);</span></span></dt><dd><p>当 pci_dev 被恢复时 PCI 核心调用的函数的指针. 它一直被调用在调用挂起之后. 这个函数时可选的; 一个驱动不必提供它.</p></dd></dl></div><p>总之, 为创建一个正确的 struct pci_driver 结构, 只有 4 个字段需要被初始化:</p><pre class="programlisting">static struct pci_driver pci_driver = { .name = "pci_skel", .id_table = ids, .probe = probe, .remove = remove,}; </pre><p>为注册 struct pci_driver 到 PCI 核心, 用一个带有指向 struct pci_driver 的指针调用 pci_register_driver. 传统上这在 PCI 驱动的模块初始化代码中完成:</p><pre class="programlisting">static int __init pci_skel_init(void){ return pci_register_driver(&amp;pci_driver);}</pre><p>注意, pci_register_driver 函数要么返回一个负的错误码, 要么是 0 当所有都成功注册. 它不返回绑定到驱动上的设备号,或者一个错误码如果没有设备被绑定到驱动上. 这是自 2.6 发布之前的内核的一个改变, 并且是因为下列的情况而来的:</p><div class="itemizedlist"><ul type="disc"><li><p>在支持 PCI 热插拔的系统上, 或者 CardBus 系统, 一个 PCI 设备可在任何时间点出现或消失. 如果驱动可在设备出现前被加载是有帮助的, 可以减少用来初始化一个设备的时间.</p></li><li><p>2.6 内核允许新 PCI ID 被动态地分配给一个驱动, 在它被加载之后. 这是通过被创建在 sysfs 中的所有 PCI 驱动目录的文件 new_id 来完成的. 如果一个新设备在被使用而内核对它还不知道时, 这是非常有用的. 一个用户可写 PCI ID 值到 new_id 文件, 并且接着驱动绑定到新设备. 如果一个驱动不被允许加载直到一个设备在系统中出现, 这个接口将不能工作.</p></li></ul></div><p>当 PCI 驱动被卸载, struct pci_drive 需要从内核中注销. 这通过调用 pci_unregister_driver 完成. 当发生这个调用, 任何当前绑定到这个驱动的 PCI 设备都被去除, 并且这个 PCI 驱动的 remove 函数在 pci_unregister_driver 函数返回之前被调用.</p><pre class="programlisting">static void __exit pci_skel_exit(void){ pci_unregister_driver(&amp;pci_driver);}</pre></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="OldStypePCIProbing.sect2"></a>12.1.6.&#160;老式 PCI 探测</h3></div></div></div><p>在老的内核版本中, 函数 pci_register_driver, 不是一直被 PCI 驱动使用. 相反, 它们要么手工浏览系统中的 PCI 设备列表, 要么它们将调用一个能够搜索一个特定 PCI 设备的函数. 驱动的浏览系统中 PCI 设备列表的能力已被从 2.6 内核中去除, 为了阻止驱动破坏内核, 如果它们偶尔修改 PCI 设备列表当一个设备同时被去除时.</p><p>如果发现一个特定 PCI 设备的能力真正被需要, 下列的函数可用:</p><div class="variablelist"><dl><dt><span class="term"><span>struct pci_dev *pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from);</span></span></dt><dd><p>这个函数扫描当前系统中 PCI 设备的列表, 并且如果输入参数匹配指定的供应商和设备 ID, 它递增在 struct pci_dev 变量 found 中的引用计数, 并且返回它给调用者. 这阻止了这个结构没有任何通知地消失, 并且确保内核不会 oops. 在驱动用完由这个函数返回的 struct pci_dev, 它必须调用函数 pci_dev_put 来正确地递减回使用计数, 以允许内核清理设备如果它被去除.参数 from 用同一个签名来得到多个设备; 这个参数应答指向已被找到的最后一个设备, 以便搜索能够继续, 而不必从列表头开始. 为找到第一个设备, from 被指定为 NULL. 如果没有找到(进一步)设备, 返回 NULL.</p><p></p><p>一个如何正确使用这个函数的例子是:</p><pre class="programlisting">struct pci_dev *dev;dev = pci_get_device(PCI_VENDOR_FOO, PCI_DEVICE_FOO, NULL);if (dev){        /* Use the PCI device */        ...        pci_dev_put(dev);}</pre><p>这个函数不能从中断上下文中被调用. 如果这样做了, 一个警告被打印到系统日志.</p></dd><dt><span class="term"><span>struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, struct pci_dev *from);</span></span></dt><dd><p>这个函数就象 pci_get_device 一样, 但是它允许当寻找设备时指定子系统供应商和子系统设备 ID. 这个函数不能从中断上下文调用. 如果是, 一个警告被打印到系统日志.</p></dd><dt><span class="term"><span>struct pci_dev *pci_get_slot(struct pci_bus *bus, unsigned int devfn);</span></span></dt><dd><p>这个函数查找系统中的 PCI 设备的列表, 在指定的 struct pci_bus 上, 一个指定的 PCI 设备的设备和功能号. 如果找到一个匹配的设备, 它的引用计数被递增并且返回指向它的一个指针. 当调用者完成存取 struct pci_dev, 它必须调用 pci_dev_put.</p></dd></dl></div><p>所有指向函数都不能从中断上下文调用. 如果是, 一个警告被打印到系统日志中.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="EnablingthePCIDevice.sect2"></a>12.1.7.&#160;使能 PCI 设备</h3></div></div></div><p>在 PCI 驱动的探测函数中, 在驱动可存取 PCI 设备的任何设备资源(I/O 区或者中断)之前, 驱动必须调用 pci_enable_device 函数:</p><div class="variablelist"><dl><dt><span class="term"><span>int pci_enable_device(struct pci_dev *dev);</span></span></dt><dd><p>这个函数实际上使能设备. 它唤醒设备以及在某些情况下也分配它的中断线和 I/O 区. 例如, 这发生在 CardBus 设备上(它在驱动层次上已经完全和 PCI 等同了).</p></dd></dl></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="AccessingtheConfigurationSpace.sect2"></a>12.1.8.&#160;存取配置空间</h3></div></div></div><p>在驱动已探测到设备后, 它常常需要读或写 3 个地址空间: 内存, 端口, 和配置. 特别地, 存取配置空间对驱动是至关重要的, 因为这是唯一的找到设备被映射到内存和 I/O 空间的位置的方法.</p><p>因为微处理器无法直接存取配置空间, 计算机供应商不得不提供一个方法来完成它. 为存取配置空间, CPU 必须写和读 PCI 控制器中的寄存器, 但是确切的实现是依赖于供应商的, 并且和这个讨论无关, 因为 Linux提供了一个标准接口来存取配置空间.</p><p>对于驱动, 配置空间可通过8-位, 16-位, 或者 32-位数据传输来存取. 相关的函数原型定义于 &lt;linux/pci.h&gt;:</p><div class="variablelist"><dl><dt><span class="term"><span>int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);</span></span></dt><dd></dd><dt><span class="term"><span>int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);</span></span></dt><dd></dd><dt><span class="term"><span>int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);</span></span></dt><dd><p>从由 dev 所标识出的设备的配置空间读 1 个, 2 个或者 4 个字节. where 参数是从配置空间开始的字节偏移. 从配置空间取得的值通过 val 指针返回, 并且这个函数的返回值是一个错误码. word 和 dword 函数转换刚刚读的值从小端到处理器的本地字节序, 因此你不必处理字节序.</p></dd><dt><span class="term"><span>int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);</span></span></dt><dd></dd><dt><span class="term"><span>int pci_write_config_word(struct pci_dev *dev, int where, u16 val);</span></span></dt><dd></dd><dt><span class="term"><span>int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);</span></span></dt><dd><p>写 1 个, 2 个或者 4 个字节到配置空间. 象通常一样, 设备由 dev 所标识, 并且象通常一样被写的值被传递. word 和 dword 函数转换这个值到小端, 在写到外设之前.</p></dd></dl></div><p>所有的之前的函数被实现为真正调用下列函数的内联函数. 可自由使用这些函数代替上面这些, 如果这个驱动在任何特别时刻不能及时存取 struct pci_dev :</p>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -