DEVICE_ATTR 宏声明有四个参数,分别是名称、权限位、读函数、写函数
28 struct attribute {
29 const char *name; //设定该文件的名字
30 struct module *owner; //设定该文件的属主
31 mode_t mode; //设定该文件的文件操作权限
32 };
/*linux/device.h*/
38 struct bus_attribute {
39 struct attribute attr;
40 ssize_t (*show)(struct bus_type *bus, char *buf);
41 ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
42 };
attribute中有两个函数指针,show和store。
当访问总线目录中的name文件时,就会触发show函数,一般会将指定的信息存放到数组buf,并传到用户空间显示。
当修改总线目录中的name文件是,就会触发stroe函数,一般会将从用户空间传来的buf指针存放的count个字节内容存放到内核中。
由此可以看到,通过这样的文件,就能实现sys目录下的文件与内核设备模型之间的数据交互。
使用驱动(PCI)的 sysfs 属性文件, bind, unbind 和 new_id
在设备驱动 /sys/bus/*/driver/... 下可以看到很多驱动都有 bind, unbind, new_id 这三个属性,
# <span class="boldcode"><strong>find /sys/bus/*/drivers/ -name bind -ls</strong></span> ... |
每一个设备驱动程序在程序内以某种方式注明了可用于哪些硬件,如所有的 PCI 驱动都使用 MODULE_DEVICE_TABLE 声明了所能驱动的 PCI 硬件的 PCI 设备号。但驱动程序不能预知未来,未来生产的新的硬件有可能兼容现有硬件的工作方式,就还可以使用现有硬件驱动程序来工作。在 bind 和 unbind 发明以前,这种情况除了修改 PCI 设备驱动程序的 DEVICE_TABLE 段落,重新编译驱动程序,以外别无他法,在 2.6 内核上添加了 bind 和 unbind 之后可以在不重新编译的情况下对设备和驱动之间进行手工方式地绑定。
而且对于有些硬件设备可以有多份驱动可用,但任何具体时刻只能有一个驱动程序来驱动这个硬件,这时可以使用 bind/unbind 来强制使用和不使用哪一个驱动程序;(注意关于多种驱动程序的选择,更好的管理方法是使用 modprobe.conf 配置文件,需要重启才生效,而 bind/unbind 提供的是一种临时的无需重启立即生效的途径;)
使用它们可以强制绑定某个设备使用或强制不使用某个驱动程序,操作方法就是通过 bind 和 unbind 接口。
#find /sys/-type f ( -name bind -or -name unbind -or -name new_id ) -ls 69 0 -rw-r--r-- 1 root root 4096 12月 12 22:12 /sys/devices/virtual/vtconsole/vtcon0/bind 3072 0 --w------- 1 root root 4096 12月 12 22:15 /sys/bus/platform/drivers/vesafb/unbind [...] 6489 0 --w------- 1 root root 4096 12月 12 22:09 /sys/bus/pci/drivers/8139too/unbind 6490 0 --w------- 1 root root 4096 12月 12 22:09 /sys/bus/pci/drivers/8139too/bind 6491 0 --w------- 1 root root 4096 12月 12 22:15 /sys/bus/pci/drivers/8139too/new_id |
这个结果中特别提到了 8139too 这份驱动程序的这三个属性文件,
# find /sys/bus/pci/drivers/8139too/ -ls 6435 0 drwxr-xr-x 2 root root 0 12月 12 22:08 /sys/bus/pci/drivers/8139too/ 6436 0 lrwxrwxrwx 1 root root 0 12月 12 22:08 /sys/bus/pci/drivers/8139too/0000:00:0e.0 -> ../../../../devices/pci0000:00/0000:00:0e.0 6485 0 lrwxrwxrwx 1 root root 0 12月 12 22:08 /sys/bus/pci/drivers/8139too/module -> ../../../../module/8139too 6488 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/uevent 6489 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/unbind 6490 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/bind 6491 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/new_id # echo 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind -bash: echo: write error: 没有那个设备 # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 link/ether 00:14:2a:d1:16:72 brd ff:ff:ff:ff:ff:ff inet 192.168.1.102/24 brd 192.168.1.255 scope global eth0 3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff # echo -n 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff # echo -n 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/bind # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff 4: eth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 00:14:2a:d1:16:72 brd ff:ff:ff:ff:ff:ff |
这一段操作过程演示了如何对 PCI 设备 "0000:00:0e.0" 强制取消绑定 "8139too" 驱动和强制绑定 "8139too" 驱动:
对 unbind 属性写入总线号码(bus_id)即是强制取消绑定;
对 bind 属性写入总线号码(bus_id)即是强制绑定;
注意,它要求的写入的是总线号码,对应于PCI设备的总线号码是按照 "domain(4位):bus(2位):slot(2位):function号(不限)" 的方式组织,是可以从其设备 kobject 节点上找到,而其它类型的总线有各自不同的规则;
请特别注意: 在这一个例子中, "echo 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind" 这第一个写入命令以 "No such device" 为错误退出,而后续的 "echo -n" 命令则可以成功。这是因为内核在对总线号码进行匹配时过于严格了,通常的 "echo" 命令写入一个字符串会以一个换行符结束输出,内核所接收到的是带有这个换行符的 bus_id 字符串,将它与内核数据结构中的真正的 bus_id 字符串相比较,当然不能找到;所幸的是,这个问题在最新的 2.6.28 开发中的内核上已已经解决,它将这个比较函数改为一个特殊实现的字符串比较,自动忽略结尾处的换行符,在 2.6.28-rc6 内核上测试,不带"-n"参数的 echo 命令已经可以写入成功。
而 new_id 属性文件也可以以另一种途径解决新的设备号问题:它是一个只写的驱动属性,可用于向其中写新的设备号。它支持写入 2至7个十六进制整形参数,分别代表 vendor, device, subvendor, subdevice, class, class_mask, driver_data 最少为 2个是因为一个 PCI设备主要以厂商号(vendor)和设备号(device)所唯一标定,其它 5个参数如果不输入则缺省值为 PCI_ANY_ID(0xffff)。
5441 0 --w------- 1 root root 4096 12月 14 18:15 /sys/bus/pci/drivers/8139too/new_id |
从 8139too 驱动上可以看到它当前所静态支持的设备号码列表,其中包括当前系统中的设备 10ec:8139, 假设未来有一款 8140 设备也满足 8139 设备的硬件通讯协议,于是可以使用 8139too 驱动程序来驱动它,操作如下
# echo '10ec 8140' > /sys/bus/pci/drivers/8139too/new_id |
这在不更新驱动程序的情况下调试设备很有用处。