Linux内核驱动接口(dev)

device_create()函数创建节点

/dev下面的设备节点一般由两种方式创建的,一者是mknod手动创建,另外就是驱动中创建新设备的时候,会往udev类的用户程序上报一个DEVICE_ADD的事件,udev接收到设备添加事件后就会在/dev下创建设备节点了

##/dev目录生成,/dev目录下的节点生成???

#devt这个是major和minor设备组合出来的值
fmt字符串就是在/dev下面生成的文件
并且在class对应的目录下生成相应的文件并建立symlink
比如在

struct device *device_create(struct class *class, struct device *parent,
                 dev_t devt, void *drvdata, const char *fmt, ...)
{
        va_list vargs;
        struct device *dev;

        va_start(vargs, fmt);
        dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
        va_end(vargs);
        return dev;
}

#################################################################################################

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
MODULE_AUTHOR("Zaiguang Hong");
MODULE_DESCRIPTION("A gentle Hello World module");
MODULE_LICENSE("GPL");
static struct miscdevice test_miscdev={
    .minor = 239,
    .name = "test_miscdev",
    .fops = NULL,
};
static int __init task_init(void)
{
    int error;
    printk(KERN_DEBUG "Hello world!\n");
    error=misc_register(&test_miscdev);
    if(error)
    pr_info("test_miscdev register failed");    
    return 0;
}
static void __exit task_cleanup(void)
{
    printk(KERN_DEBUG "Goodbye World!\n");
}
module_init(task_init);
module_exit(task_cleanup);

Makefile如下:

obj-m += test.o
KERNEL ?= /lib/modules/$(shell uname -r)/build
all:
    make -C $(KERNEL) M=$(PWD) modules
clean:
    make -C $(KERNEL) M=$(PWD) clean

像上面这一段程序编译成.ko文件之后,insmod test.ko之后会生成如下文件

misc_register(&test_miscdev);
misc->this_device = device_create(misc_class, misc->parent, dev,    
                      misc, "%s", misc->name); 
  1. 这个会在/dev目录下生成misc->name对应的node,也即是test_miscdev。
  2. class对应的目录,也就是/sys/class/misc目录下生成test_miscdev文件并弄成symlink
  3. symlink指向了/sys/devices/virtual/misc/test_miscdev并且在这个文件夹下面生成了
  4. dev power subsystem uevent 等几个文件。
    catdev=>10:239minor cat uevent =>
    MAJOR=10
    MINOR=239
    DEVNAME=test_miscdev
    subsystem文件夹symlink到上一层文件夹!!
    power文件夹下面有一个wakeup文件,$cat wakeup没有显示任何东西

//devices目录下的virtual生成可以参考get_device_parent()->virtual_device_parent
//misc_class = class_create(THIS_MODULE, “misc”);这个会在/sys/class下面生成misc文件夹
下面解释一下misc_register()是怎么一步一步完成上面的过程的

int misc_register(struct miscdevice * misc){
    ....
    int misc_register(struct miscdevice * misc);   
    //misc->parent == NULL,

    dev = MKDEV(MISC_MAJOR, misc->minor); //dev_t dev类型,表示主设备号和minor设备号的数据类型
    misc->this_device = device_create(misc_class, misc->parent, dev,
                      misc, "%s", misc->name);
    ...
}

struct device *device_create(struct class *class, struct device *parent,
                 dev_t devt, void *drvdata, const char *fmt, ...)
{
    va_list vargs;
    struct device *dev;
    va_start(vargs, fmt); //这个后续再解释
    dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
    va_end(vargs);
    return dev;
}

struct device *device_create_vargs(struct class *class, struct device *parent,
                   dev_t devt, void *drvdata, const char *fmt,
                   va_list args)
{
    struct device *dev = NULL;
    int retval = -ENODEV;
    if (class == NULL || IS_ERR(class))
        goto error;
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev) {
        retval = -ENOMEM;
        goto error;
    }
    dev->devt = devt;//保存设备号
    dev->class = class;//保存设备class
    dev->parent = parent;//parent
    dev->release = device_create_release;
    dev_set_drvdata(dev, drvdata); //dev->p->driver_data里边把保存task_miscdev
    retval = kobject_set_name_vargs(&dev->kobj, fmt, args);//这里把task_miscdev设备的名字赋值给dev->kobj->name,而不是赋值给dev->name ?? 为什么?
//而且kobj_set_name_vargs()函数里边使用了kvasprintf()函数,需要整理一下
    if (retval)
        goto error;
    retval = device_register(dev);//应该就是这里做了后续所有的事情!!
    if (retval)
        goto error;
    return dev;
error:
    put_device(dev);
    return ERR_PTR(retval);
}

int device_register(struct device *dev)
{
    device_initialize(dev);//初始化dev里边的spinlock等,但里边有比较重要的两个变量赋值
                               //dev->kobj.kset = devices_kset;
                           //kobject_init(&dev->kobj, &device_ktype);
    return device_add(dev);
}

在进入真正做事的device_add()函数之前,先解释一下device_initialize()函数中
devices_kset和device_ktype

void device_initialize(struct device *dev)
{
        dev->kobj.kset = devices_kset;
    kobject_init(&dev->kobj, &device_ktype);
        .....
}

devices_kset的解释:
/* /sys/devices/ */
struct kset *devices_kset;
devices_kset = kset_create_and_add(“devices”, &device_uevent_ops, NULL);//创建一个kset,kset->kobj->name设置为devices , kset->uevent_ops=device_uevent_ops,
kset->kobj.ktype=&kset_ktype
//kset->uevent_ops赋值为device_uevent_ops,这个就是在每个/sys/devices/目录下面的文件夹中,读取uevent的时候通过show_uevent()函数进行调用的!!!

static struct kobj_type kset_ktype= {
    .sysfs_ops  = &kobj_sysfs_ops,
    .release = kset_release,
};
const struct sysfs_ops kobj_sysfs_ops= {
    .show   = kobj_attr_show,
    .store  = kobj_attr_store,
};

device_ktype的解释:
static struct kobj_type device_ktype = {
    .release    = device_release,
    .sysfs_ops  = &dev_sysfs_ops,
    .namespace  = device_namespace,
};
static const struct sysfs_ops dev_sysfs_ops = {
    .show   = dev_attr_show,
    .store  = dev_attr_store,
};

kobject_init(&dev->kobj, &device_ktype); //这句只是初始化dev->kobj然后把device_ktype赋值给dev->kobj->ktype,
在读特定文件的时候,可以看到通过fill_read_buffer里边会调用sysfs_ops->show.
也即调用dev_sysfs_ops标量的show函数.dev_sysfs_ops函数里边还会取出来device_attribute来调用show!!所以dev_sysfs_ops只是一个媒介!!
所以这里实现device_attribute的show等函数,就可以显示需要的内容,之后再讲!

下面介绍实际的工作者:device_add()函数!!

int device_add(struct device *dev)
{
        dev = get_device(dev);//这个做什么?
        if (!dev->p) {
        error = device_private_init(dev);
            if (error)
                    goto done;
        }

    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name);
        dev->init_name = NULL;
    }
    /* subsystems can specify simple device enumeration */
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

    if (!dev_name(dev)) {
        error = -EINVAL;
        goto name_error;
    }

    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

    parent = get_device(dev->parent);
    kobj = get_device_parent(dev, parent);
    if (kobj)
        dev->kobj.parent = kobj;

    /* use parent numa_node */
    if (parent)
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); //调用create_dir(kobj)创建kobj名字的文件夹,当然这里都提前设置了parent,所以放在/sys/devices/virtual/misc文件夹下面。
    if (error)
        goto Error;

    /* notify platform of device entry */
    if (platform_notify)
        platform_notify(dev);

    error = device_create_file(dev, &uevent_attr);//在test_miscdevice文件夹下面创建uevent文件,并传入uevent_attr为device_attribute,这样在读取uevent文件的时候,就会调用uevent_attr的show函数
    if (error)
        goto attrError;

    if (MAJOR(dev->devt)) {
        error = device_create_file(dev, &devt_attr); //在test_miscdev文件夹下面生成dev文件,并传入devt_attr,所以在cat dev的时候就会调用devt_attr里边的show函数
        if (error)
            goto ueventattrError;

        error = device_create_sys_dev_entry(dev);
        if (error)
            goto devtattrError;

        devtmpfs_create_node(dev);
    }

    error = device_add_class_symlinks(dev);//做一个symlink到class里边对应的文件夹!!
    if (error)
        goto SymlinkError;
    error = device_add_attrs(dev); //根据class->attr添加attr到device目录下??
    if (error)
        goto AttrsError;
    error = bus_add_device(dev);
    if (error)
        goto BusError;
    error = dpm_sysfs_add(dev);
    if (error)
        goto DPMError;
    device_pm_add(dev);

    /* Notify clients of device addition. This call must come * after dpm_sysfs_add() and before kobject_uevent(). */
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);

    kobject_uevent(&dev->kobj, KOBJ_ADD);
    bus_probe_device(dev);
    if (parent)
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children);

    if (dev->class) {
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
        klist_add_tail(&dev->knode_class,
                   &dev->class->p->klist_devices);

        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf,
                    &dev->class->p->interfaces, node)
            if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
        mutex_unlock(&dev->class->p->mutex);
    }
}


static int device_add_attrs(struct device *dev)
{
    if (class) {
            error = device_add_attributes(dev, class->dev_attrs);
                    .....
    }
    if (type) {
        error = device_add_groups(dev, type->groups);
        .........
    }
    error = device_add_groups(dev, dev->groups);
    return 0;

}

device_create_file()和device_create_bin_file()的用法

1) 传输的数据大小比较小的时候一般用device_create_file(),使用方法如下:

static DEVICE_ATTR(_name, _mode, _show, _store);   

extern int __must_check device_create_file(struct device *device,   
                                            const struct device_attribute *entry);   

extern void device_remove_file(struct device *dev,   
                                   const struct device_attribute *attr);  

使用DEVICE_ATTR()生成相关的device_attribute数据结构,然后调用device_create_file()生成sysfs文件,如果需要删除就用device_remove_file()函数删掉相应的sysfs。

_name : device的名字,后面会根据这个名字生成dev_attr_[name]
_mode : 文件读写模式
_show와 _store : 读写相关的函数指针

读写函数一般写法如下:

    static ssize_t xxxxx_store(struct device *, struct device_attribute *,   
                            const char *buf, size_t count)   
    {   
        u8 data;   

        scanf(buf, fmt, &data);   

        return count;   
    }   

    static ssize_t xxxxx_show(struct device *, struct device_attribute *,   
                            char *buf)   
    {   
        int count;   
        u8 data;   

        count += sprintf(buf, fmt, data);   

        return count;   
    }  

这两个函数都需要返回count,但xxx_store不管是不是用count只要把原来的值返回回去就可以。但xxx_show函数需要在使用count之后,需要返回加上使用的count的值。如果不遵守返回count的规则,内部使用的data buffer会出问题。

1) 传输的数据大小比较大的时候一般用device_create_bin_file,使用方法如下:

    static struct bin_attribute xxxx_attr = {   
        .attr = {   
            .name = "xxxx",   
            .mode = S_IRWXUGO,   
        },   
        .size = MAX_BUF,   
        .read = xxx_sysfs_read,   
        .write = xxx_sysfs_write,   
        .mmap = xxx_sysfs_mmap,   
    }   
    extern int __must_check device_create_bin_file(struct device *dev,   
                                            const struct bin_attribute *attr);   
    extern void device_remove_bin_file(struct device *dev,   
                                       const struct bin_attribute *attr);  

传输过来的数据会根据MAX_BUF的大小(数据大小/MAX_BUF+1),分成多个部分传给.read, .write, .mmap函数。这个几个函数会根据(数据大小/MAX_BUF+1)的值被调相应的次数。所以在.read, .write, .mmap函数中需要操作buffer的方式不太一样。

    static ssize_t xxxx_sysfs_read(struct kobject *, struct bin_attribute *,   
               char *buf, loff_t off, size_t size)   
    {   
        int count;   
        u8 data;   

        if (off >= MAX_BUF)   
            return 0;   

        if (off + size > PAGE_SIZE)   
            return 0;   

        count += sprintf(buf, fmt, data);   

        return count;       
    }   

    static ssize_t xxxx_sysfs_write(struct kobject *, struct bin_attribute *,   
               char *buf, loff_t off, size_t size)   
    {   
        if (off == 0) {   
            /* set the values, for the first time */  
        }   

       return size;   
    }   

    static ssize_t xxxx_sysfs_mmap(struct kobject *, struct bin_attribute *,   
               struct vm_area_struct *vma)   
    {   
        /* ? */  
    }  

你可能感兴趣的:(linux,kernel)