总线设备驱动模型学习

参考国嵌教程以及网络资料学习。
https://blog.csdn.net/lidaqiang99/article/details/6599117
https://blog.csdn.net/zqixiao_09/article/details/50888795

一、总线设备驱动模型

总线是处理器和设备之间的通道,在设备模型中,所有的设备都通过总线相连,甚至是内部的虚拟“platform”总线。在linux设备模型中,总线由bus_type结构表示,定义在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 (*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_register(struct bus_type *bus)

若成功,新的总线将被添加进系统,并可在sysfs的/sys/bus下看到。

总线删除

void bus_unregister(struct bus_type *bus)

总线match方法

int (*match)(struct device *dev,struct device_driver *drv)

当一个新设备或者驱动添加到这个总线时,该方法调用。用于判断指定的驱动程序是否能处理指定的设备。若可以,则返回非零值。

总线uevent方法

int (*uevent)(struct device *dev,char **envp,int num_envp,char *buffer,int buffer_size)

在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量。

总线属性

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 conunt);//写

}

创建属性

int bus_create_file(struct bus_type *bus,struct bus_attribute *attr)

删除属性

void bus_remove_file(struct bus_type *bus,struct bus_attribute *attr)
代码示例
#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>          // __init   __exit
#include <linux/device.h> 
#include <linux/kernel.h> 
#include <linux/string.h> 
#include <linux/sysfs.h> 
#include <linux/stat.h> 

//MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("pp");                  // 描述模块的作者
MODULE_VERSION("0.1");                // 描述模块的版本
MODULE_DESCRIPTION("module bus_basic");   // 描述模块的介绍信息
MODULE_ALIAS("alias bus_basic");          // 描述模块的别名信息

static char *Version = "$Reversion: 1.0 $";

static int my_match(struct device *dev,struct device_driver *driver)
{
    //return !strncmp(dev->bus_id,driver->name,strlen(driver->name));
    return !strncmp(dev_name(dev),driver->name,strlen(driver->name));
}

struct bus_type my_bus_type = {
    .name = "my_bus",
    .match = my_match,
};

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

static int __init my_bus_init(void)
{
    int ret;
    /*注册总线*/
    ret = bus_register(&my_bus_type);
    if(ret)
       return ret;

    /*创建属性文件*/  
    if(bus_create_file(&my_bus_type,&bus_attr_version))
       printk(KERN_NOTICE "failed to create version attribute!\n");

    return ret;
}

static void my_bus_exit(void)
{
    bus_unregister(&my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);

这里由于我是在本机测试,采用Linux4.x内核,因此改动:

在新版本的内核中struct device 已经没有bus_id成员,取而代之的是通过dev_name和dev_set_name对设备的名字进行操作。
struct device - replace bus_id with dev_name(),以后只要使用dev->bus_id的时候,改成dev_name(dev)就可以了。

验证:
这里写图片描述

二、bus驱动学习

总线

bus.c

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>          // __init   __exit
#include <linux/device.h> 
#include <linux/kernel.h> 
#include <linux/string.h> 
#include <linux/sysfs.h> 
#include <linux/stat.h> 

//MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("pp");                  // 描述模块的作者
MODULE_VERSION("0.1");                // 描述模块的版本
MODULE_DESCRIPTION("module bus_basic");   // 描述模块的介绍信息
MODULE_ALIAS("alias bus_basic");          // 描述模块的别名信息

static char *Version = "$Reversion: 1.0 $";

////////////////////////////////////////////////////////
static void my_bus_release(struct device *dev)
{
    printk(KERN_DEBUG "my bus release\n");
}

struct device my_bus = {
    //.bus_id = "my_bus0",
    .release = my_bus_release,
};

////////////////////////////////////////////////////////
static int my_match(struct device *dev,struct device_driver *driver)
{
    //return !strncmp(dev->bus_id,driver->name,strlen(driver->name));
    return !strncmp(dev_name(dev),driver->name,strlen(driver->name));
}


struct bus_type my_bus_type = {
    .name = "my_bus",
    .match = my_match,
};
////////////////////////////////////////////////////////

EXPORT_SYMBOL(my_bus);
EXPORT_SYMBOL(my_bus_type);


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

static int __init my_bus_init(void)
{
    int ret;
    /*注册总线*/
    ret = bus_register(&my_bus_type);
    if(ret)
       return ret;

    /*创建属性文件*/  
    if(bus_create_file(&my_bus_type,&bus_attr_version))
       printk(KERN_NOTICE "failed to create version attribute!\n");

    //.bus_id
    dev_set_name(&my_bus,"my_bus0");
    //注册总线设备
    ret = device_register(&my_bus);
    if(ret)
       printk(KERN_NOTICE "failed to register device:my_bus!\n");

    return ret;
}

static void my_bus_exit(void)
{
    //卸载总线设备
    device_unregister(&my_bus);
    bus_unregister(&my_bus_type);

}
module_init(my_bus_init);
module_exit(my_bus_exit);
设备

device.c

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");

extern struct device my_bus; 
extern struct bus_type my_bus_type;

/* Why need this ?*/
static void my_dev_release(struct device *dev)
{ 

}

struct device my_dev = {
    .bus = &my_bus_type,
    .parent = &my_bus,
    .release = my_dev_release,
};

/*
 * Export a simple attribute.
 */
static ssize_t mydev_show(struct device *dev, char *buf)
{
    return sprintf(buf, "%s\n", "This is my device!");
}

static DEVICE_ATTR(dev, S_IRUGO, mydev_show, NULL);

static int __init my_device_init(void)
{
    int ret = 0;

    /* 初始化设备 */
    //strncpy(my_dev.bus_id, "my_dev", BUS_ID_SIZE);
    dev_set_name(&my_dev,"my_dev");

    /*注册设备*/
    device_register(&my_dev);

    /*创建属性文件*/
    device_create_file(&my_dev, &dev_attr_dev);

    return ret; 

}

static void my_device_exit(void)
{
    device_unregister(&my_dev);
}

module_init(my_device_init);
module_exit(my_device_exit);
驱动

driver.c

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");

extern struct bus_type my_bus_type;

static int my_probe(struct device *dev)
{
    printk("Driver found device which my driver can handle!\n");
    return 0;
}

static int my_remove(struct device *dev)
{
    printk("Driver found device unpluged!\n");
    return 0;
}

struct device_driver my_driver = {
    .name = "my_dev",
    .bus = &my_bus_type,
    .probe = my_probe,
        .remove = my_remove,
};

/*
 * Export a simple attribute.
 */
static ssize_t mydriver_show(struct device_driver *driver, char *buf)
{
    return sprintf(buf, "%s\n", "This is my driver!");
}

static DRIVER_ATTR(drv, S_IRUGO, mydriver_show, NULL);

static int __init my_driver_init(void)
{
    int ret = 0;

    /*注册驱动*/
    driver_register(&my_driver);

    /*创建属性文件*/
    driver_create_file(&my_driver, &driver_attr_drv);

    return ret; 

}

static void my_driver_exit(void)
{
    driver_unregister(&my_driver);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

Makefile

#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build    

obj-m   += bus.o device.o  driver.o

all:
    make -C $(KERN_DIR) M=`pwd` modules 

.PHONY: clean    
clean:
#   make -C $(KERN_DIR) M=`pwd` clean
    rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
    rm -f Module.markers Module.symvers modules.order
    rm -rf .tmp_versions Modules.symvers

make之后得到bus.ko, dev.ko, drv.ko文件

验证

使用root用户依次调用如下命令添加3个模块:

 insmod bus.ko
 insmod dev.ko
 insmod.drv.ko

输出:

pp@pp:~/learndriver/bustest$ ls /sys/bus/my_bus/drivers/my_dev/
bind  drv  my_dev  uevent  unbind
pp@pp:~/learndriver/bustest$ ls /sys/bus/my_bus/devices/my_dev/
dev  driver  power  subsystem  uevent

可以看到
/sys/bus/my_bus/drivers/目录下有my_dev设备了
/sys/bus/my_bus/drivers/目录下有my_dev驱动了

三、Platform驱动学习

driver.c

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");

static int my_probe(struct device *dev)
{
    printk("Driver found device which my driver can handle!\n");
    return 0;
}

static int my_remove(struct device *dev)
{
    printk("Driver found device unpluged!\n");
    return 0;
}

static struct platform_driver my_driver = {
    .probe      = my_probe,
    .remove     = my_remove,
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = "my_dev",
    },
};

static int __init my_driver_init(void)
{       
        /*注册平台驱动*/
    return platform_driver_register(&my_driver);
}

static void my_driver_exit(void)
{
    platform_driver_unregister(&my_driver);
}

module_init(my_driver_init);
module_exit(my_driver_exit);

device.c

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("Dual BSD/GPL");

static struct platform_device *my_device;

static int __init my_device_init(void)
{
    int ret = 0;

        /* 分配结构 */
    my_device = platform_device_alloc("my_dev", -1);

        /*注册设备*/
    ret = platform_device_add(my_device);

    /*注册失败,释放相关内存*/
    if (ret)
        platform_device_put(my_device);

    return ret; 
}

static void my_device_exit(void)
{
    platform_device_unregister(my_device);
}

module_init(my_device_init);
module_exit(my_device_exit);

我们要记住,platform 驱动只是在字符设备驱动外套一层platform_driver 的外壳。

可以看到,模块加载和卸载函数仅仅通过paltform_driver_register()、paltform_driver_unregister() 函数进行 platform_driver 的注册和注销,而原先注册和注销字符设备的工作已经被移交到 platform_driver 的 probe() 和 remove() 成员函数中。

引入platform模型符合Linux 设备模型 —— 总线、设备、驱动,设备模型中配套的sysfs节点都可以用,方便我们的开发;当然你也可以选择不用,不过就失去了一些platform带来的便利;

设备驱动中引入platform 概念,隔离BSP和驱动。在BSP中定义platform设备和设备使用的资源、设备的具体匹配信息,而在驱动中,只需要通过API去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

猜你喜欢

转载自blog.csdn.net/u013457167/article/details/80398592