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都会简单不少。透过那层层迷雾,会发现他其实很简单。
阅读(6484) | 评论(0) | 转发(1) |