写在前面:最近时间较为充裕,所以对一直很想分析的dm9000的网卡驱动做了一次深入的分析,并总结如下。博文内容大致总结如下:
1.sysfs的对于内核管理方式;
2.kobject、kset、kobject三者的概念;
3.从bus总线的创建开始,从sys的角度看 platform平台总线的创建过程,以及一些内核提供的注册函数的分析;
4.以2.6.32.7版本内核中的dm9000网卡为例,具体的分析网卡驱动。
在正式开始分析网卡的实际驱动之前,对于一些基本但是很重要的概念需要做一些解释。 首先要说的就是dm9000使用的是platform平台总线来进行管理的,所以在正式分析之前下进行platform平台总线的分析:
1.What is sysfs?
个人理解:sysfs=sys+fs,即一种文件的管理方式,类似于rootfs(根文件系统提供/根目录),众所周知内核负责管理设备和对应的驱动,而这个信息对用户时不可见的,而通过/sys文件夹,操作系统向用户提供的一个查看内核信息的一个窗口,用户可以直观的了解到设备驱动的层次结构。(下图是常见/sys目录的文件情况)
其中:block:代表块设备文件; bus:系统中的各种总线,如I2c; class:系统中的设备类型,如杂散类设备misc; dev:系统中已经注册的设备节点的情况,包含char和block目录(linux中的设备文件不是char字符型就是bloak块设备型); devices:系统中所有设备的一个拓扑结构视图; fireware:系统中的固件; fs:文件系统; kernel:内核配置状态和状态信息; module:模块; power:系统的电源管理数据。
2.kobjetc、kset、ktype
说到sysfs就不得不说说kobjetc、kset、ktype。
kobject:是一个对象的抽象,用户管理对象,在sys目录中对应一个目录;
kset:一些kobject的集合,其中的kobject可以是同一个ktype的,也可以不同。同时kset自己也包含一个kobjct,在sysfs中kset也代表着自己包含的这个目录,但是在目录下包含着其他的kobject;
ktype:每一个描述kobjetc对象的结构体内都有这样一个结构体变量,用来定义kobject在创建和删除时采取的动作。(结构体内用于作kobject的引用计数的变量为0时,通过release方法来释放相关的资源。)
最下面的kobj都属于一个kset,同时这些kobj的父对象就是kset内嵌的kobj。
以class目录为例:class目录就是sysfs中的一个kset(也就是kset中内嵌的kobject),其中有着其他的类的目录文件夹,同时也可以存放数据文件。其中的每一个类对应的文件相当于一个kobject(例如net目录),其中存放着具体的设备文件(例如net中的eth0)。
3.总线、驱动、设备:
3.1总线bus:
总线的本质是bus_type结构体类型,各种不同的总线其实就是bus_type的各种实例。它是处理器与一个设备或多个设备进行通讯的通道。在驱动中总线将驱动和设备进行连接。
platform作为一种特殊的总线(并无实际的物理意义,而是专用在驱动的管理中的一种总线管理方式),也属于bus的一种,在/sys目录中有着属于platform目录(这时platform就是一个kset,其中的device和driver目录就相当于一个kobject,而其中也有着其他的普通文件),用于管理各种平台总总线架构的设备。
下面以platform平台总线为例,从sysfs的角度分析整个总线的创建过程:
3.1.1、创建bus目录:首先platform平台总线也属于bus总线目录中的一种,所以我么从最基本的bus目录的创建开始进行分析:(drivers/base/bus.c)
int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;
return 0;
}
其中最重要的是调用了kset_create_and_add函数,而这个函数又调用了 kset_create函数和kset_register函数完成kset的创建和注册。
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int error;
kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);
这是内核提供给驱动开发者的一个申请kset的一个函数。(kobject.c文件中)
而后又在头文件中定义了一个所有总线的表述结构体:struct bus_type用来描述每一个属于bus的具体总线(include/linux/device.h)
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);
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 bus_type_private *p;
};
我们看到每个bus_type都包含一个kset对象subsys,该kset在/sys/bus/目录下有着对应的一个目录,目录名即为字段name。而platform就属于其中一个。
3.1.2、platform目录的创建,platform平台总线的注册:由platform_bus_init函数完成的。该函数在内核启动阶段被调用,我们来简单看下调用过程:(platform.c)
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
bus_register是bus总线给提供的一种注册总线的函数(这部分是由内核开发者自己实现的,而我们大多的情况是使用创建好的platform平台总线架构来管理驱动和设备),以在/sys/bus/下创建不同类的总线。这里直接调用bus_register完成platform平台总线的创建:(kobject的注册,注册的名字就是platform)
int bus_register(struct bus_type *bus)
{
int retval;
struct bus_type_private *priv;
priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/*互相保存*/
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
/*设定kobject->name*/
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
/*注册kset,在bus/建立目录XXX,XXX为bus->name*/
retval = kset_register(&priv->subsys);
if (retval)
goto out;
/*创建属性,在bus/XXX/建立文件uevent*/
retval =