LDD3读书笔记------总线, 设备, 和驱动,类

总线

一个总线是处理器和一个或多个设备之间的通道. 为设备模型的目的, 所有的设备都通过一个总线连接。设备模型表示在总线和它们控制的设备之间的实际连接.

在 Linux 设备模型中, 一个总线由 bus_type 结构代表, 定义在 <linux/device.h>. 这个结构看来象:

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 */
};

name 成员是总线的名子,  你可从这个结构中见到每个总线是它自己的子系统; 这个子系统不位于 sysfs 的顶层, 但是. 相反, 它们在总线子系统下面. 一个总线包含 2 个 ksets, 代表已知的总线的驱动和所有插入总线的设备.

总线注册注销

ret = bus_register(&ldd_bus_type);
if (ret)
return ret;

void bus_unregister(struct bus_type *bus);

总线方法

int (*match)(struct device *device, struct device_driver *driver);
这个方法被调用, 大概多次, 无论何时一个新设备或者驱动被添加给这个总线. 它应当返回一个非零值。如果给定的设备可被给定的驱动处理. 这个函数必须在总线级别处理, 因为那是合适的逻辑存在的地方; 核心内核不能知道如何匹配每个可能总线类型的设备和驱动.

int (*hotplug) (struct device *device, char **envp, int num_envp, char *buffer, int buffer_size);
这个模块允许总线添加变量到环境中, 在产生一个热插拔事件在用户空间之前. 参数和 kset 热插拔方法相同( 在前面的 "热插拔事件产生" 一节中描述 ).

列举设备和驱动

为操作每个对总线已知的设备, 使用:

int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));

这个函数列举总线上的每个设备, 传递关联的设备结构给 fn, 连同作为 data 来传递的值. 如果 start 是 NULL, 列举从总线的第一个设备开始; 否则列举从 start 之后的第一个设备开始. 如果 fn 返回一个非零值, 列举停止并且那个值从 bus_for_each_dev 返回.

有一个类似的函数来列举驱动:

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));

这个函数就像 buf_for_each_dev, 除了, 当然, 它替之作用于驱动.

应当注意, 这 2 个函数持有总线子系统的读者/写者旗标在工作期间. 因此试图一起使用这 2 个会死锁 -- 每个将试图获取同一个旗标. 修改总线的操作( 例如注销设备 )也将锁住. 因此, 小心使用 bus_for_each 函数.

总线属性

几乎 Linux 驱动模型中的每一层都提供一个添加属性的接口, 并且总线层不例外. bus_attribute 类型定义在 <linux/device.h> 如下:

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);
};

我们已经见到 struct attribute 在 "缺省属性" 一节. bus_attribute 类型也包含 2 个方法来显示和设置属性值. 大部分在 kobject 之上的设备模型层以这种方式工作.

已经提供了一个方便的宏为在编译时间创建和初始化 bus_attribute 结构:

BUS_ATTR(name, mode, show, store);

这个宏声明一个结构, 产生它的名子通过前缀字符串 bus_attr_ 到给定的名子.

任何属于一个总线的属性应当明确使用 bus_create_file 来创建:

int bus_create_file(struct bus_type *bus, struct bus_attribute *attr); 

属性也可被去除, 使用:

void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr); 

 

设备

Linux 系统中的每个设备由一个 struct device 代表:

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 */

};

有许多其他的 struct device 成员只对设备核心代码感兴趣. 但是, 这些成员值得了解:

struct device *parent

设备的 "parent" 设备 -- 它所附着到的设备. 在大部分情况, 一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 设备是一个顶层设备, 这常常不是你所要的.

struct kobject kobj;

代表这个设备并且连接它到层次中的 kobject. 注意, 作为一个通用的规则, device->kobj->parent 等同于 device->parent->kobj.

char bus_id[BUS_ID_SIZE];

唯一确定这个总线上的设备的字符串. PCI 设备, 例如, 使用标准的 PCI ID 格式, 包含域, 总线, 设备, 和功能号.

struct bus_type *bus;

确定设备位于哪种总线.

struct device_driver *driver;

管理这个设备的驱动; 我们查看 struct device_driver

void *driver_data;

一个可能被设备驱动使用的私有数据成员.

void (*release)(struct device *dev);

当对这个设备的最后引用被去除时调用的方法; 它从被嵌入的 kobject 的 release 方法被调用. 注册到核心的所有的设备结构必须有一个 release 方法, 否则内核打印出慌乱的抱怨.

最少, parent, bus_id, bus, 和 release 成员必须在设备结构被注册前设置.

设备注册

通常的注册和注销函数在:

int device_register(struct device *dev);
void device_unregister(struct device *dev);

 设备属性

sysfs 中的设备入口可有属性. 相关的结构是:

struct device_attribute {
 struct attribute attr;
 ssize_t (*show)(struct device *dev, char *buf);
 ssize_t (*store)(struct device *dev, const char *buf,
 size_t count);
};

这些属性结构可在编译时建立, 使用这些宏:

DEVICE_ATTR(name, mode, show, store);

结果结构通过前缀 dev_attr_ 到给定名子上来命名. 属性文件的实际管理使用通常的函数对来处理:

int device_create_file(struct device *device, struct device_attribute *entry);
void device_remove_file(struct device *dev, struct device_attribute *attr);

struct bus_type 的 dev_attrs 成员指向一个缺省的属性列表, 这些属性给添加到总线的每个设备创建.

设备结构嵌入

device这个结构, 如同 kobject 结构, 常常是嵌入一个更高级的设备表示中. 如果你查看 struct pci_dev 的定义或者 struct usb_device 的定义, 你会发现一个 struct device 埋在其中.

设备驱动

设备模型跟踪所有对系统已知的驱动. 这个跟踪的主要原因是使驱动核心能匹配驱动和新设备

驱动由下列结构定义:

struct device_driver {
 char *name;
 struct bus_type *bus;
 struct kobject kobj;
 struct list_head devices;
 int (*probe)(struct device *dev);
 int (*remove)(struct device *dev);
 void (*shutdown) (struct device *dev);
}; 

再一次, 几个结构成员被忽略( 全部内容见 <linux/device.h> ). 这里, name 是驱动的名子( 它在 sysfs 中出现 ), bus 是这个驱动使用的总线类型, kobj 是必然的 kobject, devices 是当前绑定到这个驱动的所有设备的列表, probe 是一个函数被调用来查询一个特定设备的存在(以及这个驱动是否可以使用它), remove 当设备从系统中去除时被调用, shutdown 在关闭时被调用来关闭设备.

使用 device_driver 结构的函数的形式, 现在应当看来是类似的(因此我们快速涵盖它们). 注册函数是:

int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

通常的属性结构在:

struct driver_attribute {
 struct attribute attr;
 ssize_t (*show)(struct device_driver *drv, char *buf);
 ssize_t (*store)(struct device_driver *drv, const char *buf,

 size_t count);
};
DRIVER_ATTR(name, mode, show, store);

以及属性文件以通常的方法创建:

int driver_create_file(struct device_driver *drv, struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv, struct driver_attribute *attr);

bus_type 结构含有一个成员( drv_attrs ) 指向一套缺省属性, 对所有关联到这个总线的驱动都创建.

驱动结构嵌入

如同大部分驱动核心结构的情形, device_driver 结构常常被发现嵌到一个更高级的, 总线特定的结构.

个类是一个设备的高级视图, 它抽象出低级的实现细节.类允许用户空间基于它们做什么来使用设备, 而不是它们如何被连接或者它们如何工作.

在许多情况, 类子系统是最好的输出信息到用户空间的方法.

class_simple 接口

class_simple 接口意图是易于使用, 以至于没人会抱怨没有暴露至少一个包含设备的被分配的号的属性. 使用这个接口只不过是一对函数调用, 没有通常的和 Linux 设备模型关联的样板.

第一步是创建类自身. 使用一个对 class_simple_create 的调用来完成:

struct class_simple *class_simple_create(struct module *owner, char *name);

这个函数使用给定的名子创建一个类. 这个操作可能失败, 当然, 因此在继续之前返回值应当一直被检查( 使用 IS_ERR, 在第 1 章的"指针和错误值"一节中描述过).

一个简单的类可被销毁, 使用:

void class_simple_destroy(struct class_simple *cs); 

创建一个简单类的真实目的是添加设备给它; 这个任务使用:

struct class_device *class_simple_device_add(struct class_simple *cs, dev_t devnum, struct device *device, const char *fmt, ...); 

这里, cs 是之前创建的简单类, devnum 是分配的设备号, device 是代表这个设备的 struct device, 其他的参数是一个 printk-风格 的格式串和参数来创建设备名子. 这个调用添加一项到类, 包含一个属性, dev, 含有设备号. 如果设备参数是非 NULL, 一个符号连接( 称为 device )指向在 /sys/devices 下的设备的入口.

可能添加其他的属性到设备入口. 它只是使用 class_device_create_file, 我们在下一节和完整类子系统所剩下的内容讨论.

当设备进出时类产生热插拔事件. 如果你的驱动需要添加变量到环境中给用户空间事件处理者, 可以建立一个热插拔回调, 使用:

int class_simple_set_hotplug(struct class_simple *cs,
 int (*hotplug)(struct class_device *dev,
 char **envp, int num_envp,
 char *buffer, int buffer_size)); 

当你的设备离开时, 类入口应当被去除, 使用:

void class_simple_device_remove(dev_t dev); 

注意, 由 class_simple_device_add 返回的 class_device 结构这里不需要; 设备号(它当然应当是唯一的)足够了.

完整的类接口

 管理类

一个类由一个 struct class 的实例来定义:

struct class {
char *name;
struct class_attribute *class_attrs;
struct class_device_attribute *class_dev_attrs;
int (*hotplug)(struct class_device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
void (*release)(struct class_device *dev);
void (*class_release)(struct class *class);
/* Some fields omitted */
};

每个类需要一个唯一的名子, 它是这个类如何在 /sys/class 中出现. 当这个类被注册, 由 class_attrs 所指向的数组中列出的所有属性被创建. 还有一套缺省属性给每个添加到类中的设备; class_dev_attrs 指向它们. 有通常的热插拔函数来添加变量到环境中, 当事件产生时. 还有 2 个释放方法: release 在无论何时从类中去除一个设备时被调用, 而 class_release 在类自己被释放时调用.

注册函数是:

int class_register(struct class *cls);
void class_unregister(struct class *cls);

使用属性的接口不应当在这点吓人:

struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *cls, char *buf);
ssize_t (*store)(struct class *cls, const char *buf, size_t count);
};
CLASS_ATTR(name, mode, show, store);
int class_create_file(struct class *cls, const struct class_attribute *attr);
void class_remove_file(struct class *cls, const struct class_attribute *attr);

类设备

一个类的真正目的是作为一个是该类成员的设备的容器. 一个成员由 struct class_device 来表示:

struct class_device {
struct kobject kobj;
struct class *class;
struct device *dev;
void *class_data;
char class_id[BUS_ID_SIZE];

 };

class_id 成员持有设备名子, 如同它在 sysfs 中的一样. class 指针应当指向持有这个设备的类, 并且 dev 应当指向关联的设备结构. 设置 dev 是可选的; 如果它是非 NULL, 它用来创建一个符号连接从类入口到对应的在 /sys/devices 下的入口, 使得易于在用户空间找到设备入口. 类可以使用 class_data 来持有一个私有指针.

通常的注册函数已经被提供:

int class_device_register(struct class_device *cd);
void class_device_unregister(struct class_device *cd);

类设备接口也允许重命名一个已经注册的入口:

int class_device_rename(struct class_device *cd, char *new_name); 

类设备入口有属性:

struct class_device_attribute {
 struct attribute attr;
 ssize_t (*show)(struct class_device *cls, char *buf);
 ssize_t (*store)(struct class_device *cls, const char *buf,
 size_t count);
};

CLASS_DEVICE_ATTR(name, mode, show, store); 
int class_device_create_file(struct class_device *cls, const struct class_device_attribute *attr);
void class_device_remove_file(struct class_device *cls, const struct class_device_attribute *attr);

一个缺省的属性集合, 在类的 class_dev_attrs 成员, 被创建当类设备被注册时; class_device_create_file 可用来创建额外的属性. 属性还可以被加入到由 class_simple 接口创建的类设备.

类接口

类子系统有一个额外的在 Linux 设备模型其他部分找不到的概念. 这个机制称为一个接口, 但是它是, 也许, 最好作为一种触发机制可用来在设备进入或离开类时得到通知.

一个接口被表示, 使用:

struct class_interface {
 struct class *class;
 int (*add) (struct class_device *cd);
 void (*remove) (struct class_device *cd); 
}; 

接口可被注册或注销, 使用:

int class_interface_register(struct class_interface *intf);
void class_interface_unregister(struct class_interface *intf);

一个接口的功能是简单明了的. 无论何时一个类设备被加入到在 class_interface 结构中指定的类时, 接口的 add 函数被调用. 这个函数可进行任何额外的这个设备需要的设置; 这个设置常常采取增加更多属性的形式, 但是其他的应用都可能. 当设备被从类中去除, remove 方法被调用来进行任何需要的清理.

可注册多个接口给一个类.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值