Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3404800
  • 博文数量: 754
  • 博客积分: 10132
  • 博客等级: 上将
  • 技术积分: 7780
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-14 23:36
文章分类

全部博文(754)

文章存档

2012年(3)

2011年(39)

2010年(66)

2009年(167)

2008年(479)

我的朋友

分类: LINUX

2009-03-23 11:44:36

文章的例子和实验使用《LDD3》所配的lddbus模块(稍作修改)。

总线

总线是处理器和一个或多个之间的通道,在设备中, 所有的设备都通过总线相连, 甚至是内部的虚拟"platform"总线。总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。
在 设备模型中, 总线由 bus_type 结构表示, 定义在

structbus_type{
    constchar        *name;/*总线类型名称*/
    structmodule        *owner;/*指向模块的指针(如果有), 此模块负责操作这个总线*/

    structkset        subsys;/*与该总线相关的子系统*/
    structkset        drivers;/*总线的kset*/
    structkset        devices;/* 挂在该总线的所有设备的kset*/

    structklist        klist_devices;/*与该总线相关的驱动程序链表*/
    structklist        klist_drivers;/*挂接在该总线的设备链表*/

    structblocking_notifier_head bus_notifier;

    structbus_attribute    *bus_attrs;/*总线属性*/
    structdevice_attribute*dev_attrs;/*设备属性,指向为每个加入总线的设备建立的默认属性链表*/
    structdriver_attribute*drv_attrs;/*驱动程序属性*/
    structbus_attribute drivers_autoprobe_attr;/*驱动自动探测属性*/
    structbus_attribute drivers_probe_attr;/*驱动探测属性*/

    int        (*match)(structdevice*dev,structdevice_driver*drv);
    int        (*uevent)(structdevice*dev,char**envp,
                intnum_envp,char*buffer,intbuffer_size);
    int        (*probe)(structdevice*dev);
    int        (*remove)(structdevice*dev);
    void        (*shutdown)(structdevice*dev);

    int(*suspend)(structdevice*dev,pm_message_t state);
    int(*suspend_late)(structdevice*dev,pm_message_t state);
    int(*resume_early)(structdevice*dev);
    nt(*resume)(structdevice*dev);
/*处理热插拔、电源、探测和移除等事件的方法*/
    unsignedintdrivers_autoprobe:1;
};

总线的注册和删除

总线的主要注册步骤:

(1)申明和初始化 bus_type 结构体。只有很少的 bus_type 成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:

structbus_type ldd_bus_type={
    .name="ldd",
    .match=ldd_match,
    .uevent=ldd_uevent,
};

(2)调用bus_register函数注册总线。

intbus_register(structbus_type*bus)

调用可能失败, 所以必须始终检查返回值。若成功,新的总线子将被添加进系统,并可在 sysfs 的 /sys/bus 下看到。之后可以向总线添加设备。
例如:

ret=bus_register(&ldd_bus_type);
if(ret)
 returnret;

 
当必须从系统中删除一个总线时, 调用:

voidbus_unregister(structbus_type*bus);

总线方法

在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:

int(*match)(structdevice*dev,structdevice_driver*drv);
/*当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数, 因为那里存在正确的逻辑,核心不知道如何为每个总线类型匹配设备和驱动程序*/

int(*uevent)(structdevice*dev,char**envp,intnum_envp,char*buffer,intbuffer_size);
/*在为空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/

lddbus的match和uevent方法:

staticintldd_match(structdevice*dev,structdevice_driver*driver)
{
 return!strncmp(dev->bus_id,driver->name,strlen(driver->name));
}/*仅简单比较驱动和设备的名字*/
/*当涉及实际硬件时, match 函数常常对设备提供的硬件 ID 和驱动所支持的 ID 做比较*/

staticintldd_uevent(structdevice*dev,char**envp,intnum_envp,char*buffer,intbuffer_size)
{
 envp[0]=buffer;
 if(snprintf(buffer,buffer_size,"LDDBUS_VERSION=%s",
 Version)>=buffer_size)
 return-ENOMEM;
 envp[1]=NULL;
 return0;
}/*在环境变量中加入 lddbus 源码的当前版本号*/

对设备和驱动的迭代

若要编写总线层代码, 可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 bus_type 结构中的其他数据结构, 但最好使用内核提供的辅助函数:

intbus_for_each_dev(structbus_type*bus,structdevice*start,void*data,int(*fn)(structdevice*,void*));
intbus_for_each_drv(structbus_type*bus,structdevice_driver*start,void*data,int(*fn)(structdevice_driver*,void*));

/* 这两个函数迭代总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 传递给 fn, 同时传递 data 值。若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。*/

总线属性

几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在如下:

structbus_attribute{
    structattribute    attr;
    ssize_t(*show)(structbus_type*,char*buf);
    ssize_t(*store)(structbus_type*,constchar*buf,size_tcount);
};


可以看出structbus_attribute 和structattribute 很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。

内核提供了一个宏在时创建和初始化 bus_attribute 结构:

BUS_ATTR(_name,_mode,_show,_store)/*这个宏声明一个结构, 将 bus_attr_ 作为给定 _name 的前缀来创建总线的真正名称*/

/*总线的属性必须显式调用 bus_create_file 来创建:*/
intbus_create_file(structbus_type*bus,structbus_attribute*attr);

/*删除总线的属性调用:*/
voidbus_remove_file(structbus_type*bus,structbus_attribute*attr);

例如创建一个包含源码版本号简单属性文件方法如下:

staticssize_t show_bus_version(structbus_type*bus,char*buf)
{
 returnsnprintf(buf,PAGE_SIZE,"%s\n",Version);
}

staticBUS_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 代表:

structdevice{
    structklist        klist_children;
    structklist_node    knode_parent;   /* node in sibling list */
    structklist_node    knode_driver;
    structklist_node    knode_bus;
    structdevice        *parent;/* 设备的 "父" 设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见 */

    structkobject kobj;/*代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj*/
    char    bus_id[BUS_ID_SIZE];/*在总线上唯一标识该设备的字符串;例如: PCI 设备使用标准的 PCI ID 格式, 包含:域, 总线, 设备, 和功能号.*/
    structdevice_type    *type;
    unsigned        is_registered:1;
    unsigned        uevent_suppress:1;
    structdevice_attribute uevent_attr;
    structdevice_attribute*devt_attr;

    structsemaphore    sem;  /* semaphore to synchronize calls to its driver. */
    structbus_type    *bus;     /*标识该设备连接在何种类型的总线上*/
    structdevice_driver*driver;    /*管理该设备的驱动程序*/
    void        *driver_data;    /*该设备驱动使用的私有数据成员*/
    void        *platform_data;    /* Platform specific data, device core doesn't touch it */
    structdev_pm_info    power;

#ifdefCONFIG_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. */


    structlist_head    dma_pools;    /* dma pools (if dma'ble) */

    structdma_coherent_mem    *dma_mem;/* internal for coherent mem override */
    /* arch specific additions */
    structdev_archdata    archdata;

    spinlock_t        devres_lock;
    structlist_head    devres_head;

    /* class_device migration path */
    structlist_head    node;
    structclass        *class;
    dev_t          devt;       /* dev_t, creates the sysfs "dev" */
    structattribute_group    **groups;    /* optional groups */

    void    (*release)(structdevice*dev);/*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/
};
/*在注册 struct device 前,最少要设置parent, bus_id, bus, 和 release 成员*/

设备注册

设备的注册和注销函数为:

intdevice_register(structdevice*dev);
voiddevice_unregister(structdevice*dev);

一个实际的总线也是一个设备,所以必须单独注册,以下为 lddbus 在编译时注册它的虚拟总线设备源码:

staticvoidldd_bus_release(structdevice*dev)
{
 printk(KERN_DEBUG"lddbus release\n");
}

structdevice 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 下显示*/

设备属性

sysfs 中的设备入口可有属性,相关的结构是:

/* interface for exporting device attributes这个结构体和《LDD3》中的不同,已经被更新过了,请特别注意!*/
structdevice_attribute{
    structattribute attr;
    ssize_t(*show)(structdevice*dev,struct device_attribute *attr,char*buf);
    ssize_t(*store)(structdevice*dev,struct device_attribute *attr,constchar*buf,size_t count);
};

/*设备属性结构可在编译时建立, 使用以下宏:*/
DEVICE_ATTR(_name,_mode,_show,_store);
/*这个宏声明一个结构, 将 dev_attr_ 作为给定 _name 的前缀来命名设备属性

/*属性文件的实际处理使用以下函数:*/

 intdevice_create_file(structdevice*device,    structdevice_attribute*entry);
 voiddevice_remove_file(structdevice*dev,structdevice_attribute*attr);

设备结构的嵌入

device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们又拥有的设备的额外信息,所以很少单纯用 device 结构代表设备,而是,通常将其嵌入一个设备的高层表示中。底层驱动几乎不知道 struct device。

lddbus 驱动创建了它自己的 device 类型,并期望每个设备驱动使用这个类型来注册它们的设备:

structldd_device{
 char*name;
 structldd_driver*driver;
 structdevice dev;
};
#defineto_ldd_device(dev)container_of(dev,structldd_device,dev);

lddbus 导出的注册和注销接口如下:

/*
 * LDD devices.
 */


/*
 * For now, no references to LDDbus devices go out which are not
 * tracked via the module reference count, so we use a no-op
 * release function.
 */

staticvoidldd_dev_release(structdevice*dev)
{}

intregister_ldd_device(structldd_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);
    returndevice_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);

voidunregister_ldd_device(structldd_device*ldddev)
{
    device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);


 sculld 驱动添加一个自己的属性到它的设备入口,称为 dev, 仅包含关联的设备号,源码如下:

staticssize_t sculld_show_dev(structdevice*ddev,struct device_attribute *attr,char*buf)
{
 structsculld_dev*dev=ddev->driver_data;
 returnprint_dev_t(buf,dev->cdev.dev);
}

staticDEVICE_ATTR(dev,S_IRUGO,sculld_show_dev,NULL);

/*接着, 在初始化时间, 设备被注册, 并且 dev 属性通过下面的函数被创建:*/
staticvoidsculld_register_dev(structsculld_dev*dev,intindex)
{
 sprintf(dev->devname,"sculld%d",index);
 dev->ldev.name=dev->devname;
 dev->ldev.driver=&sculld_driver;
 dev->ldev.dev.driver_data=dev;
 register_ldd_device(&dev->ldev);
 if (device_create_file(&dev->ldev.dev, &dev_attr_dev))
    printk( "Unable to create dev attribute ! \n");
}/*注意:程序使用 driver_data 成员来存储指向我们自己的内部的设备结构的指针。请检查
device_create_file的返回值,否则编译时会有警告。*/


设备驱动程序

设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱动和新设备之间的关系。一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体 device_driver 定义如下:

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

/*定义在*/
structdevice_driver{
    constchar        *name;/*驱动程序的名字( 在 sysfs 中出现 )*/
    structbus_type        *bus;/*驱动程序所操作的总线类型*/

    structkobject        kobj;/*内嵌的kobject对象*/
    structklist        klist_devices;/*当前驱动程序能操作的设备链表*/
    structklist_node    knode_bus;

    structmodule        *owner;
    constchar  

TAG: