📄 ch14s04.html
字号:
<html xmlns:cf="http://docbook.sourceforge.net/xmlns/chunkfast/1.0"><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>14.4. 总线, 设备, 和驱动-Linux设备驱动第三版(中文版)</title><meta name="description" content="驱动开发" /><meta name="keywords" content="Linux设备驱动,中文版,第三版,ldd,linux device driver,驱动开发,电子版,程序设计,软件开发,开发频道" /><meta name="verify-v1" content="5asbXwkS/Vv5OdJbK3Ix0X8osxBUX9hutPyUxoubhes=" /><link rel="stylesheet" href="docbook.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.69.0"><link rel="start" href="index.html" title="Linux 设备驱动 Edition 3"><link rel="up" href="ch14.html" title="第 14 章 Linux 设备模型"><link rel="prev" href="ch14s03.html" title="14.3. 热插拔事件产生"><link rel="next" href="ch14s05.html" title="14.5. 类"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">14.4. 总线, 设备, 和驱动</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch14s03.html">上一页</a> </td><th width="60%" align="center">第 14 章 Linux 设备模型</th><td width="20%" align="right"> <a accesskey="n" href="ch14s05.html">下一页</a></td></tr></table><hr></div><div class="sect1" lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="BuesDevicesandDrivers.sect"></a>14.4. 总线, 设备, 和驱动</h2></div></div></div><p>至今, 我们已经看到大量低级框架和一个相对少的例子. 我们试图在本章剩下部分中补充, 随着我们进入 Linux 设备模型的更高级. 为此, 我们介绍一个新的虚拟总线, 我们称为 lddbus, <sup>[<a name="id485223" href="#ftn.id485223">46</a>]</sup>并且修改 scullp 驱动来 "接入" 到这个总线.</p><p>再一次, 许多驱动作者将不会需要这里涉及的材料. 这个水平的细节通常在总线级别处理, 并且很少作者需要添加一个新总线类型. 这个信息是有用的, 但是, 对任何人好奇在 PCI, USB 等层面的里面发生了什么或者谁需要在那个级别做改变. </p><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="Buses.sect"></a>14.4.1. 总线</h3></div></div></div><p>一个总线是处理器和一个或多个设备之间的通道. 为设备模型的目的, 所有的设备都通过一个总线连接, 甚至当它是一个内部的虚拟的,"平台"总线. 总线可以插入另一个 - 一个 USB 控制器常常是一个 PCI 设备, 例如. 设备模型表示在总线和它们控制的设备之间的实际连接.</p><p></p><p>在 Linux 设备模型中, 一个总线由 bus_type 结构代表, 定义在 <linux/device.h>. 这个结构看来象:</p><pre class="programlisting">struct bus_type { char *name; struct subsystem subsys; struct kset drivers; struct kset devices; int (*match)(struct device *dev, struct device_driver *drv); struct device *(*add)(struct device * parent, char * bus_id); int (*hotplug) (struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size); /* Some fields omitted */};</pre><p>name 成员是总线的名子, 有些同 pci. 你可从这个结构中见到每个总线是它自己的子系统; 这个子系统不位于 sysfs 的顶层, 但是. 相反, 它们在总线子系统下面. 一个总线包含 2 个 ksets, 代表已知的总线的驱动和所有插入总线的设备. 所以, 有一套方法我们马上将涉及.</p><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Busregisteration.sect"></a>14.4.1.1. 总线注册</h4></div></div></div><p>如同我们提过的, 例子源码包含一个虚拟总线实现称为 lddbus. 这个总线建立它的 bus_type 结构, 如下:</p><pre class="programlisting">struct bus_type ldd_bus_type = { .name = "ldd", .match = ldd_match, .hotplug = ldd_hotplug, };</pre><p>注意很少 bus_type 成员要求初始化; 大部分由设备模型核心处理. 但是, 我们确实不得不指定总线的名子, 以及任何伴随它的方法.</p><p>不可避免地, 一个新总线必须注册到系统, 通过一个对 bus_register 的调用. lddbus 代码这样做以这样的方式:</p><pre class="programlisting">ret = bus_register(&ldd_bus_type);if (ret) return ret; </pre><p>这个调用可能失败, 当然, 因此返回值必须一直检查. 如果它成功, 新总线子系统已被添加到系统; 在 sysfs 中 /sys/bus 的下面可以见到, 并且可能启动添加设备.</p><p>如果有必要从系统中去除一个总线(当关联模块被去除, 例如), 调用调用 bus_unregister:</p><pre class="programlisting">void bus_unregister(struct bus_type *bus); </pre></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Busmethods.sect"></a>14.4.1.2. 总线方法</h4></div></div></div><p>有几个给 bus_type 结构定义的方法; 它们允许总线代码作为一个设备核心和单独驱动之间的中介. 在 2.6.10 内核中定义的方法是:</p><div class="variablelist"><dl><dt><span class="term"><span>int (*match)(struct device *device, struct device_driver *driver);</span></span></dt><dd><p>这个方法被调用, 大概多次, 无论何时一个新设备或者驱动被添加给这个总线. 它应当返回一个非零值如果给定的设备可被给定的驱动处理. (我们马上进入设备和 device_driver 结构的细节). 这个函数必须在总线级别处理, 因为那是合适的逻辑存在的地方; 核心内核不能知道如何匹配每个可能总线类型的设备和驱动.</p></dd><dt><span class="term"><span>int (*hotplug) (struct device *device, char **envp, int num_envp, char *buffer, int buffer_size);</span></span></dt><dd><p>这个模块允许总线添加变量到环境中, 在产生一个热插拔事件在用户空间之前. 参数和 kset 热插拔方法相同( 在前面的 "热插拔事件产生" 一节中描述 ).</p></dd></dl></div><p>lddbus 驱动有一个非常简单的匹配函数, 它仅仅比较驱动和设备的名子:</p><pre class="programlisting">static int ldd_match(struct device *dev, struct device_driver *driver){ return !strncmp(dev->bus_id, driver->name, strlen(driver->name));}</pre><p>当涉及到真实硬件, match 函数常常在有设备自身提供的硬件 ID 和驱动提供的 ID 之间, 做一些比较.</p><p>lddbus 热插拔方法看来象这样:</p><pre class="programlisting">static int ldd_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size){ envp[0] = buffer; if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s", Version) >= buffer_size) return -ENOMEM; envp[1] = NULL; return 0;}</pre><p>这里, 我们加入 lddbus 源码的当前版本号, 只是以防有人好奇.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Iteratingoverdevicesanddrivers.sect"></a>14.4.1.3. 列举设备和驱动</h4></div></div></div><p>如果你在编写总线级别的代码, 你可能不得不对所有已经注册到你的总线的设备或驱动进行一些操作. 它可能会诱惑人直接进入 bus_type 结构中的各种结构, 但是最好使用已经提供的帮助函数.</p><p>为操作每个对总线已知的设备, 使用:</p><pre class="programlisting">int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));</pre><p>这个函数列举总线上的每个设备, 传递关联的设备结构给 fn, 连同作为 data 来传递的值. 如果 start 是 NULL, 列举从总线的第一个设备开始; 否则列举从 start 之后的第一个设备开始. 如果 fn 返回一个非零值, 列举停止并且那个值从 bus_for_each_dev 返回.</p><p>有一个类似的函数来列举驱动:</p><pre class="programlisting">int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));</pre><p>这个函数就像 buf_for_each_dev, 除了, 当然, 它替之作用于驱动.</p><p>应当注意, 这 2 个函数持有总线子系统的读者/写者旗标在工作期间. 因此试图一起使用这 2 个会死锁 -- 每个将试图获取同一个旗标. 修改总线的操作( 例如注销设备 )也将锁住. 因此, 小心使用 bus_for_each 函数.</p></div><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Busattributes.sect"></a>14.4.1.4. 总线属性</h4></div></div></div><p>几乎 Linux 驱动模型中的每一层都提供一个添加属性的接口, 并且总线层不例外. bus_attribute 类型定义在 <linux/device.h> 如下:</p><pre class="programlisting">struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *bus, char *buf); ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);};</pre><p>我们已经见到 struct attribute 在 "缺省属性" 一节. bus_attribute 类型也包含 2 个方法来显示和设置属性值. 大部分在 kobject 之上的设备模型层以这种方式工作.</p><p>已经提供了一个方便的宏为在编译时间创建和初始化 bus_attribute 结构:</p><pre class="programlisting">BUS_ATTR(name, mode, show, store);</pre><p>这个宏声明一个结构, 产生它的名子通过前缀字符串 bus_attr_ 到给定的名子.</p><p>任何属于一个总线的属性应当明确使用 bus_create_file 来创建:</p><pre class="programlisting">int bus_create_file(struct bus_type *bus, struct bus_attribute *attr); </pre><p>属性也可被去除, 使用:</p><pre class="programlisting">void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr); </pre><p>lddbus 驱动创建一个简单属性文件, 再次, 包含源码版本号. show 方法和 bus_attribute 结构设置如下:</p><pre class="programlisting">static ssize_t show_bus_version(struct bus_type *bus, char *buf){ return snprintf(buf, PAGE_SIZE, "%s\n", Version);}static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); </pre><p>创建属性文件在模块加载时间完成:</p><pre class="programlisting">if (bus_create_file(&ldd_bus_type, &bus_attr_version)) printk(KERN_NOTICE "Unable to create version attribute\n");</pre><p>这个调用创建一个属性文件(/sys/busldd/version) 包含 lddbus 代码的版本号.</p></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="Devices.sect"></a>14.4.2. 设备</h3></div></div></div><p>在最低层, Linux 系统中的每个设备由一个 struct device 代表:</p>struct device { struct device *parent; struct kobject kobj; char bus_id[BUS_ID_SIZE]; struct bus_type *bus; struct device_driver *driver; void *driver_data; void (*release)(struct device *dev); /* Several fields omitted */}; <p>有许多其他的 struct device 成员只对设备核心代码感兴趣. 但是, 这些成员值得了解:</p><div class="variablelist"><dl><dt><span class="term"><span>struct device *parent </span></span></dt><dd><p>设备的 "parent" 设备 -- 它所附着到的设备. 在大部分情况, 一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 设备是一个顶层设备, 这常常不是你所要的.</p></dd><dt><span class="term"><span>struct kobject kobj;</span></span></dt><dd><p>代表这个设备并且连接它到层次中的 kobject. 注意, 作为一个通用的规则, device->kobj->parent 等同于 device->parent->kobj.</p></dd><dt><span class="term"><span>char bus_id[BUS_ID_SIZE];</span></span></dt><dd><p>唯一确定这个总线上的设备的字符串. PCI 设备, 例如, 使用标准的 PCI ID 格式, 包含域, 总线, 设备, 和功能号.</p></dd><dt><span class="term"><span>struct bus_type *bus;</span></span></dt><dd><p>确定设备位于哪种总线.</p></dd><dt><span class="term"><span>struct device_driver *driver;</span></span></dt><dd><p>管理这个设备的驱动; 我们查看 struct device_driver 在下一节.</p></dd><dt><span class="term"><span>void *driver_data;</span></span></dt><dd><p>一个可能被设备驱动使用的私有数据成员.</p></dd><dt><span class="term"><span>void (*release)(struct device *dev);</span></span></dt><dd><p>当对这个设备的最后引用被去除时调用的方法; 它从被嵌入的 kobject 的 release 方法被调用. 注册到核心的所有的设备结构必须有一个 release 方法, 否则内核打印出慌乱的抱怨.</p></dd></dl></div><p>最少, parent, bus_id, bus, 和 release 成员必须在设备结构被注册前设置.</p><div class="sect3" lang="zh-cn"><div class="titlepage"><div><div><h4 class="title"><a name="Deviceregisteration.sect"></a>14.4.2.1. 设备注册</h4></div></div></div><p>通常的注册和注销函数在:</p><pre class="programlisting">int device_register(struct device *dev);void device_unregister(struct device *dev);</pre><p>我们已经见到 lddbus 代码如何注册它的总线类型. 但是, 一个实际的总线是一个设备并且必须单独注册. 为简单起见, lddbus 模块只支持一个单个虚拟总线, 因此这个驱动在编译时建立它的设备:</p><pre class="programlisting">static void ldd_bus_release(struct device *dev){ printk(KERN_DEBUG "lddbus release\n");}struct device ldd_bus = { .bus_id = "ldd0",
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -