总线
总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连, 甚至是内部的虚拟"platform"总线。总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。
在 Linux 设备模型中, 总线由 bus_type 结构表示, 定义在 <linux/device.h> :
struct bus_type {
const char * name;/*总线类型名称*/
struct module * owner;/*指向模块的指针(如果有), 此模块负责操作这个总线*/
struct kset subsys;/*与该总线相关的子系统*/
struct kset drivers;/*总线驱动程序的kset*/
struct kset devices;/* 挂在该总线的所有设备的kset*/
struct klist klist_devices;/*挂接在该总线的设备链表*/
struct klist klist_drivers;/*与该总线相关的驱动程序链表*/
struct blocking_notifier_head bus_notifier;
struct bus_attribute * bus_attrs; /*总线属性*/
struct device_attribute * dev_attrs; /*设备属性,指向为每个加入总线的设备建立的默认属性链表*/
struct driver_attribute * drv_attrs; /*驱动程序属性*/
struct bus_attribute drivers_autoprobe_attr;/*驱动自动探测属性*/
struct bus_attribute drivers_probe_attr;/*驱动探测属性*/
int (*match)(struct device * dev, struct device_driver * drv);
int (*uevent)(struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
int (*probe)(struct device * dev);
int (*remove)(struct device * dev);
void (*shutdown)(struct device * dev);
int (*suspend)(struct device * dev, pm_message_t state);
int (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
nt (*resume)(struct device * dev);
/*处理热插拔、电源管理、探测和移除等事件的方法*/
unsigned int drivers_autoprobe:1;
};
总线的注册和删除
总线的主要注册步骤:
(1)申明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
.uevent = ldd_uevent,
};
(2)调用bus_register函数注册总线。
int bus_register(struct bus_type * bus)
调用可能失败, 所以必须始终检查返回值。若成功,新的总线子系统将被添加进系统,并可在 sysfs 的 /sys/bus 下看到。之后可以向总线添加设备。
例如:
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
当必须从系统中删除一个总线时, 调用:
void bus_unregister(struct bus_type *bus);
总线方法
在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:
int (*match)(struct device * dev, struct device_driver * drv);
/*当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数, 因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序*/
int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
/*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/
lddbus的match和uevent方法:
static int ldd_match(struct device *dev, struct device_driver *driver)
{
return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}/*仅简单比较驱动和设备的名字*/
/*当涉及实际硬件时, match 函数常常对设备提供的硬件 ID 和驱动所支持的 ID 做比较*/
static int ldd_uevent(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;
}/*在环境变量中加入 lddbus 源码的当前版本号*/
对设备和驱动的迭代
若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构, 但最好使用内核提供的辅助函数:
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));
/*这两个函数迭代总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 传递给 fn, 同时传递 data 值。若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。*/
总线属性
几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 <linux/device.h> 如下:
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *, char * buf);
ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};
可以看出struct bus_attribute 和struct attribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。
内核提供了一个宏在编译时创建和初始化 bus_attribute 结构:
BUS_ATTR(_name,_mode,_show,_store)/*这个宏声明一个结构, 将 bus_attr_ 作为给定 _name 的前缀来创建总线的真正名称*/
/*总线的属性必须显式调用 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);
例如创建一个包含源码版本号简单属性文件方法如下:
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);
/*在模块加载时创建属性文件:*/
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Unable to create version attribute/n");
/*这个调用创建一个包含 lddbus 代码的版本号的属性文件(/sys/bus/ldd/version)*/
--------------------------------------------------------------------------------
设备
在最底层, Linux 系统中的每个设备由一个 struct device 代表:
struct device {
struct klist klist_children;
struct klist_node knode_parent; /* node in sibling list */
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent;/* 设备的 "父" 设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见 */
struct kobject kobj;/*代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj*/
char bus_id[BUS_ID_SIZE];/*在总线上唯一标识该设备的字符串;例如: PCI 设备使用标准的 PCI ID 格式, 包含:域, 总线, 设备, 和功能号.*/
struct device_type *type;
unsigned is_registered:1;
unsigned uevent_suppress:1;
struct device_attribute uevent_attr;
struct device_attribute *devt_attr;
struct semaphore sem; /* semaphore to synchronize calls to its driver. */
struct bus_type * bus; /*标识该设备连接在何种类型的总线上*/
struct device_driver *driver; /*管理该设备的驱动程序*/
void *driver_data; /*该设备驱动使用的私有数据成员*/
void *platform_data; /* Platform specific data, device core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */
/* arch specific additions */
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
/* class_device migration path */
struct list_head node;
struct class *class;
dev_t devt; /* dev_t, creates the sysfs "dev" */
struct attribute_group **groups; /* optional groups */
void (*release)(struct device * dev);/*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/
};
/*在注册 struct device 前,最少要设置parent, bus_id, bus, 和 release 成员*/
设备注册
设备的注册和注销函数为:
int device_register(struct device *dev);
void device_unregister(struct device *dev);
一个实际的总线也是一个设备,所以必须单独注册,以下为 lddbus 在编译时注册它的虚拟总线设备源码:
static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG "lddbus release/n");
}
struct device ldd_bus = {
.bus_id = "ldd0",
.release = ldd_bus_release
}; /*这是顶层总线,parent 和 bus 成员为 NULL*/
/*作为第一个(并且唯一)总线, 它的名字为 ldd0,这个总线设备的注册代码如下:*/
ret = device_register(&ldd_bus);
if (ret)
printk(KERN_NOTICE "Unable to register ldd0/n");
/*一旦调用完成, 新总线会在 sysfs 中 /sys/devices 下显示,任何挂到这个总线的设备会在 /sys/devices/ldd0 下显示*/