1 总线

   总线,是处理器与一个或者多个设备之间的通道。在Linux设备模型中,所有的设备都通过总线相连,甚至是那些内部的虚拟"platform"总线。用bus_type结构来描述。

   struct bus_type {
	const char		*name;         /* 总线名 */
	const char		*dev_name;    
	struct device		*dev_root;
	struct bus_attribute	*bus_attrs;  /* 总线属性 */
	struct device_attribute	*dev_attrs;   /* 设备属性 */
	struct driver_attribute	*drv_attrs;    /* 驱动属性 */

   /* 当向该总线注册设备或总线调用,用于设备与驱动的匹配 */
	int (*match)(struct device *dev, struct device_driver *drv);
   /* 当添加或移除设备时,调用此函数向用户空间产生环境变量 */
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
   /* 当设备与驱动匹配上时调用,初始化匹配设备 */
	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 (*resume)(struct device *dev);

   /* 设备、驱动的电源管理操作集 */
	const struct dev_pm_ops *pm;


	struct iommu_ops *iommu_ops;

   /* 私有数据指针,这个比较重要的一个成员 */
	struct subsys_private *p;
}; 

2. 总线声明

     内核中的每一种总线类型(PCI,I2C,USB,etc)都用bus_type结构体来描述,需要初始化相应的name成员和match函数。

      struct bus_type pci_bus_type = {
       .name    = "pci",
       .match   = pci_bus_match,
      };

3. 注册和注销函数

     int bus_register(struct bus_type *bus);

     void bus_unregister(struct bus_type *bus);

    总线 注册函数分析

int bus_register(struct bus_type *bus)
{
         retval =kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
 
         priv->subsys.kobj.kset = bus_kset;
         priv->subsys.kobj.ktype =&bus_ktype;
         priv->drivers_autoprobe = 1;
 
         /* 注册bus kset,在bus/下产生bus->name目录 */
         retval = kset_register(&priv->subsys);
 
         retval = bus_create_file(bus,&bus_attr_uevent);
 
         /* 产生device目录 */
         priv->devices_kset =kset_create_and_add("devices", NULL,
                                                         &priv->subsys.kobj);
 
         /* 产生driver目录 */
         priv->drivers_kset =kset_create_and_add("drivers", NULL,
                                                         &priv->subsys.kobj);
 
         /* 初始化设备链和驱动链 */
         klist_init(&priv->klist_devices,klist_devices_get, klist_devices_put);
         klist_init(&priv->klist_drivers,NULL, NULL);
 
         retval = add_probe_files(bus);
 
         retval = bus_add_attrs(bus);
}

Klist_drivers和klist_devices这两条链非常重要,用来遍历drivers_kset和devices_kset,drivers_kset和devices_kset分别是总线上所有driver和device的集合。


4. match() 函数

    match函数为总线提供了检测驱动是否支持此设备的功能,通过device IDs来进行匹配。当驱动被注册到总线上,总线上的设备链表会被遍历,match()被调用来完成对每个设备的匹配。


5. subsys_private

    subsys_private是一个比较重要的成员。

    struct subsys_private {
	struct kset subsys;
	struct kset *devices_kset;      /* 设备集,包含总线上所有的设备 */
	struct list_head interfaces;
	struct mutex mutex;


	struct kset *drivers_kset;  /* 驱动集,包含总线所有的驱动 */
	struct klist klist_devices;  /* 设备链表,连接总线上所有的设备 */
	struct klist klist_drivers;    /* 驱动链表,连接总线上所有的驱动 */
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1;
	struct bus_type *bus;       /* 指回bus_type */


	struct kset glue_dirs;
	struct class *class;
};

设备链表和驱动链表连接了注册到总线上的所有设备和驱动,经常用来遍历和查询总线上的设备和驱动。设备模型核心层提供了两个API,用来遍历设备和驱动。

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


6. 导出属性,总线的属性用bus_attribute来描述

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);  /* 写属性方法 */
};

BUS_ATTR宏可以在编译时刻创建和初始化bus_attribute结构体

BUS_ATTR(_name, _mode, _show, _store)

在LDM里提供了两个接口用来在sysfs目录下产生和移除属性文件

int bus_create_file(struct bus_type *, struct bus_attribute *);
void bus_remove_file(struct bus_type *, struct bus_attribute *);

7. 实例分析

创建一条scbus总线,并添加版本属性。实际中我们并不需要自己创建总线,此试验仅用来学习

/*
* for learn bus
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
 
static char *Version = "revision 1.0,scbus";
 
/* 匹配函数,通过设备名和驱动名来匹配 */
static int scbus_match(struct device *dev,struct device_driver *driver)
{
         printk("\n%s,%s\n", dev_name(dev), driver->name);
         return!strncmp(dev_name(dev), driver->name, strlen(driver->name));
}
 
static void scbus_release(struct device*dev)
{
         printk("scbusrelease\n");
}
 
struct bus_type scbus_type = {
         .name       = "scbus",
         .match   = scbus_match,
};
EXPORT_SYMBOL_GPL(scbus_type);
 
struct device scbus = {
         .init_name        = "scbus0",
         .release   = scbus_release,
};
EXPORT_SYMBOL_GPL(scbus);
 
/*
* export bus attribute
*/
static ssize_t show_bus_version(structbus_type *bus, char *buf)
{
         returnsnprintf(buf, PAGE_SIZE, "%s\n", Version);
}
static BUS_ATTR(version, S_IRUGO,show_bus_version, NULL);
 
static int __init scbus_init(void)
{
         intret;
 
         ret= bus_register(&scbus_type);
         if(ret)
                   returnret;
 
         ret= bus_create_file(&scbus_type, &bus_attr_version);
         if(ret)
                   gotocreate_error;
 
         ret= device_register(&scbus);
         if(ret)
                   gotodevice_error;
 
         printk("Createa scbus\n");
         return0;
 
device_error:
         bus_remove_file(&scbus_type,&bus_attr_version);
create_error:
         bus_unregister(&scbus_type);
         returnret;
}
 
static void __exit scbus_exit(void)
{
         device_unregister(&scbus);
         bus_remove_file(&scbus_type,&bus_attr_version);
         bus_unregister(&scbus_type);
         printk("Removea scbus\n");
}
 
module_init(scbus_init);
module_exit(scbus_exit);
 
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("CJOK<cjok.liao@gmail.com>");

试验结果:



Logo

更多推荐