Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1829096
  • 博文数量: 195
  • 博客积分: 4227
  • 博客等级: 上校
  • 技术积分: 2835
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-04 10:39
文章分类

全部博文(195)

文章存档

2013年(1)

2012年(26)

2011年(168)

分类: LINUX

2011-01-30 10:40:43

linux  bus
谨以此文纪念过往的岁月。
在linux中bus很重要,主要是bus连接了devices和drivers,devices和drivers通过bus来进行一对一的匹配。
记录学习bus的过程,如有错误请指正。
1.bus的注册以及bus属性文件的创建
注册一个bus一如注册一个设备和驱动一样,在内核中调用API就可以,如果仅仅满足于知道怎么去调用API则不是一个好的驱动工程师。
在注册bus时,注意一个区别xx_bus和xx_bus_type,对于这两个区别,在看其结构时就应该分清楚,xx_bus是设备类型,xx_bus_type是总行类型,xx_bus作为以后其隶属于该bus的设备的父设备,而xx_bus_type则是描述给总线类型。打个不恰当的例子,一个是真正的设备,一个是用于描述该设备的。对于一个简单的bus驱动,需要定义xx_bus_type,对于该结构体中的函数,有些需要实现有些则不需要,根据不同的总线,实现不同的函数。如:
struct bus_type ldd_bus_type = {
 .name = "ldd",
 .match = ldd_match,
};
上面的ldd_bus_type只实现了一个驱动和设备匹配的函数ldd_match。
其中struct bus_type 的定义如下:
struct bus_type {
 const char  *name;       --总线名
 struct bus_attribute *bus_attrs;    --总线属性
 struct device_attribute *dev_attrs;  --总线设备属性
 struct driver_attribute *drv_attrs;  --总线驱动属性
  以下的函数会在设备注册或驱动注册的时候调用。
 int (*match)(struct device *dev, struct device_driver *drv);      --实现设备与驱动的匹配。不同的总线实现匹配的方法不同,如platform总线采用name匹配,而usb_bus采用id匹配
 int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 int (*probe)(struct device *dev);                           --在2.6的内核中实现一个设备与驱动的探测。主要是因为热插拔的设备的增多。
 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);                     
 int (*resume)(struct device *dev);
 
 struct dev_pm_ops *pm;                                   --电源管理
 struct bus_type_private *p;                            --bus_type私有成员,这个结构体中主要包括了kset以及klist,用于管理其挂载其总线下的设备和驱动
};
struct bus_type_private {
 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;
 unsigned int drivers_autoprobe:1;
 struct bus_type *bus;
};
上面两个结构体很重要,这个涉及到真正的设备和驱动管理。
内核中采用bus_register来注册一个新的总线。其使用如:ret = bus_register(&ldd_bus_type);
bus_register函数的源码如下(剔除一些错误处理):
int bus_register(struct bus_type *bus)
{
 int retval;
 struct bus_type_private *priv;
 priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
 priv->bus = bus;
 bus->p = priv;
 BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
 retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);   --设置kobject名称,有时会使用snprintf来设置,不过会出现bug,最好使用该函数。
 priv->subsys.kobj.kset = bus_kset;  --kobj中的kset指向父kset
 priv->subsys.kobj.ktype = &bus_ktype;  --kobj中的ktype指向父ktype
 priv->drivers_autoprobe = 1;          --设置总线下的设备和驱动自动探测
 retval = kset_register(&priv->subsys);    --注册kset,关于kset,kobject,以及ktype的关系以后在学习
 retval = bus_create_file(bus, &bus_attr_uevent);   --创建bus的文件属性
  
 priv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj); --在父kset下创建和加入名为devices和drivers的kset
 priv->drivers_kset = kset_create_and_add("drivers", NULL,&priv->subsys.kobj);
 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);   --初始化klist,这个函数主要是实现一些赋值及初始化的功能
 klist_init(&priv->klist_drivers, NULL, NULL);
 retval = add_probe_files(bus);   --主要是创建该bus下probe的属性文件,可以通过cat file 和cat x > file 向属性文件读出和写入数据。
 retval = bus_add_attrs(bus);     --添加bus自带的属性文件
 return 0;
}
下面函数其实是实现一个从链表的节点,查询到该节点所属的设备,并且减少dev的计数。
static void klist_devices_get(struct klist_node *n)
{
 struct device *dev = container_of(n, struct device, knode_bus);
 get_device(dev);
}
其实bus的注册比较简单,说到底还是kset和kobject控制着这一切,抛开kset和kobject的具体实现,bus还是很好理解的。
如果说bus注册完后,仍然想为bus添加其他的属性文件,则可以通过bus_create_file来创建该bus的属性文件。
例如:
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);
通过BUS_ATTR宏定义定义一个属性文件,其参数依次是属性文件名,属性文件的mode,显示属性文件函数和存储属性文件函数。然后通过下面的函数调用就可以了,下面的函数其实是通过调用sysfs_create_file来实现的。
bus_create_file(&ldd_bus_type, &bus_attr_version);
到此bus_type算是注册了,单有bus类型还是不够得,还要注册一个真正的bus设备。
亦如注册一个简单设备一样,定义一个设备,然后在注册就可以了,
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
};
上面是一个定义,然后调用device_register(&ldd_bus)就可以了。
以上就是一个总线的注册。不过在实现上面的总线注册后,还有实现隶属于该总线的设备和驱动是如何注册和卸载的。
如该总线下的设备如何注册:
int register_ldd_device(struct ldd_device *ldddev)
{
 ldddev->dev.bus = &ldd_bus_type;         --这个是必须的,因为设备必须要知道这个设备是哪种总线类型。
 ldddev->dev.parent = &ldd_bus;           --这个也是必须的,设备的父设备是总线设备
 ldddev->dev.release = ldd_dev_release;   --是设备释放的时候调用
 strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);  --这个是设备名
 return device_register(&ldddev->dev);
}
void unregister_ldd_device(struct ldd_device *ldddev)
{
 device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);
EXPORT_SYMBOL(unregister_ldd_device);
而驱动的注册如下:
int register_ldd_driver(struct ldd_driver *driver)
{
 int ret;
 
 driver->driver.bus = &ldd_bus_type;      --这个是必须的
 ret = driver_register(&driver->driver);   
 if (ret)
  return ret;                                
 下面的就是该总线类型所独有的一些操作,不同的总线会有不同的驱动操作。
 driver->version_attr.attr.name = "version";
 driver->version_attr.attr.owner = driver->module;
 driver->version_attr.attr.mode = S_IRUGO;
 driver->version_attr.show = show_version;
 driver->version_attr.store = NULL;
 return driver_create_file(&driver->driver, &driver->version_attr);
}
void unregister_ldd_driver(struct ldd_driver *driver)
{
 driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);
驱动和设备结构体如下:
struct ldd_driver {
 char *version;
 struct module *module;
 struct device_driver driver;
 struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);
struct ldd_device {
 char *name;
 struct ldd_driver *driver;
 struct device dev;
};
不过像这种总线下的设备以及驱动的注册以及设备及驱动的结构体,都可以自己想怎么整就怎么整,不过有有些函数是必须调用的如:
device_register和driver_register以及他们的反函数。这几个函数才是设备驱动的真正核心,其余的只不过是在这两个函数之上的一些增加而已。以platform总线为例platform设备注册调用platform_device_register,驱动注册调用platform_driver_register最终都会调用device_register和driver_register。
以上即是一个简单的总线注册,在linux内核中,总线无处不在,他将设备与驱动连接,其实不管总线的复杂与否,其根本的本质脱离不了其上的模式。如果理解以上的总线,在去理解usb_bus,platform_bus都会简单不少。透过那层层迷雾,会发现他其实很简单。

 
阅读(6485) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~