欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android音频驱动-ASOC之创建设备节点

程序员文章站 2022-07-14 16:26:28
...

创建设备文件的方法:
第一种是使用mknod手工创建:mknod filename type major minor

第二种是自动创建设备节点:利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。
具体udev相关知识这里不详细阐述,可以移步Linux 文件系统与设备文件系统 —— udev 设备文件系统,这里主要讲使用方法。
在驱动用加入对udev 的支持主要做的就是:在驱动初始化的代码里调用class_create(…)为该设备创建一个class,再为每个设备调用device_create(…)创建对应的设备。内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create()函数,去/sysfs下寻找对应的类从而创建设备节点。

利用cat /proc/devices查看申请到的设备名,设备号。

例1,创建单个字符设备

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    MODULE_LICENSE ("GPL");
    int hello_major = 555;
    int hello_minor = 0;
    int number_of_devices = 1;
    struct cdev cdev;
    dev_t dev = 0;
    struct file_operations hello_fops = {
      .owner = THIS_MODULE
    };
    static void char_reg_setup_cdev (void)
    {
       int error, devno = MKDEV (hello_major, hello_minor);
       cdev_init (&cdev, &hello_fops);
       cdev.owner = THIS_MODULE;
       cdev.ops = &hello_fops;
       error = cdev_add (&cdev, devno , 1);//将设备加入到内核
       if (error)
           printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
     }
    struct class *my_class;
    static int __init hello_2_init (void)
    {
         int result;
         dev = MKDEV (hello_major, hello_minor);
         result = register_chrdev_region (dev, number_of_devices, "hello");//主设备号为dev,次设备号为0
         if (result<0) {
             printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
             return result;
         }
         char_reg_setup_cdev ();
         /* create your own class under /sysfs */
         my_class = class_create(THIS_MODULE, "my_class");
         if(IS_ERR(my_class))
        {
             printk("Err: failed in creating class.\n");
             return -1;
         }
         /* register your own device in sysfs, and this will cause udev to create corresponding device node */
        device_create( my_class, NULL, MKDEV(hello_major, 0), "hello" "%d", 0 );
        printk (KERN_INFO "Registered character driver\n");
        return 0;
    }
    static void __exit hello_2_exit (void)
    {
        dev_t devno = MKDEV (hello_major, hello_minor);
        cdev_del (&cdev);
        device_destroy(my_class, MKDEV(adc_major, 0));         //delete device node under /dev
        class_destroy(my_class);                               //delete class created by us
        unregister_chrdev_region (devno, number_of_devices);
       printk (KERN_INFO "char driver cleaned up\n");
    }
   module_init (hello_2_init);
   module_exit (hello_2_exit);

这样,模块加载后,就能在/dev目录下找到hello0这个设备节点了。

例2,创建多个字符设备

drivers/i2c/i2c-dev.c
/*
 * module load/unload record keeping
 */
static int __init i2c_dev_init(void)
{
     int res;
     printk(KERN_INFO "i2c /dev entries driver\n");
     res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
     if (res)
          goto out;
     i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");  //创建一个名称为i2c-dev的class
     if (IS_ERR(i2c_dev_class)) {
          res = PTR_ERR(i2c_dev_class);
         goto out_unreg_chrdev;
     }
     res = i2c_add_driver(&i2cdev_driver);
     if (res)
         goto out_unreg_class;
     return 0;
out_unreg_class:
    class_destroy(i2c_dev_class);
out_unreg_chrdev:
    unregister_chrdev(I2C_MAJOR, "i2c");
out:
    printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
    return res;
}
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
    struct i2c_dev *i2c_dev;
    int res;
    i2c_dev = get_free_i2c_dev(adap);
    if (IS_ERR(i2c_dev))
        return PTR_ERR(i2c_dev);
    /* register this i2c device with the driver core */
    i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
         MKDEV(I2C_MAJOR, adap->nr), NULL,
         "i2c-%d", adap->nr);
    if (IS_ERR(i2c_dev->dev)) {
         res = PTR_ERR(i2c_dev->dev);
         goto error;
     }
     res = device_create_file(i2c_dev->dev, &dev_attr_name);
     if (res)
         goto error_destroy;
     pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
     adap->name, adap->nr);
     return 0;
error_destroy:
    device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
    return_i2c_dev(i2c_dev);
    return res;
}

在i2cdev_attach_adapter调用device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
“i2c-%d”, adap->nr);
这样在dev目录就产生i2c-0 或i2c-1节点

例3
之前写字符设备驱动,都是使用register_chrdev向内核注册驱动程序中构建的file_operations结构体,之后创建的设备文件,只要是主设备号相同(次设备号不同),则绑定的都是同一个file_operations结构体,应用程序使用的也都是这一个结构体中注册的函数。这就会出现这样的一个弊端:同一类字符设备(即主设备号相同),会在内核中连续注册了256(分析内核代码中可知),也就是所以的此设备号都会被占用,而在大多数情况下都不会用到这么多次设备号,所以会造成极大的资源浪费。所以register_chrdev在某个角度上是有弊端的,这也是老版本内核中使用。

register_chrdev的注册,还分为静态注册和动态注册。而register_chrdev_region和alloc_chrdev_region正相当于将register_chrdev拆分来,它们分别是静态和动态注册的个体,但同时也解决了register_chrdev资源浪费的缺点。
register_chrdev_region允许注册一个规定的设备号的范围,也就不一定把0~255个此设备号都注册占用。

//from:要分配的设备号范围的起始值。
//count:所要求的连续设备编号个数。
//name:和该编号范围相关的设备名称。
register_chrdev_region(dev_t from, unsigned count, const char * name)

在2.6之后的内核,利用的是一个struct cdev结构体来描述一个字符设备。

struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
void cdev_init(struct cdev *, const struct file_operations *);//清空cdev,并填充file_operations 结构体
int cdev_add(struct cdev *, dev_t, unsigned);//注册字符设备到内核

写一个简单的字符设备驱动,主设备号为major,只注册0~1两个此设备号,并创建主设备号为major,次设备号创建0,1,2三个设备文件。
利用应用程序打开这三个文件,看有什么现象(是否都能打开)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>
static int hello_open(struct inode *inode, struct file *filp)
{
    printk("hello_open\n");
    return 0;
}
//构建file_operations结构体
static struct file_operations hello_fops={
    .owner=THIS_MODULE,
    .open   =   hello_open,
};
static int major = 252;
static struct cdev hello_cdev;
static struct class* hello_class;
static struct class_device* hello_class_dev[3];
static int hello_init(void)
{
    dev_t devid;
    if(major==0)
    {   
        alloc_chrdev_region(&devid,0,2,"hello");
        major=MAJOR(devid);
    }
    else
    {
        devid=MKDEV(major,0);//获取设备号
        //主设备号为major,次设备号为0,1,对应file_operations
        register_chrdev_region(devid,2,"hello");
    }
    cdev_init(&hello_cdev,&hello_fops);//字符设备初始化
    cdev_add(&hello_cdev,devid,2);//添加字符设备到内核中
    hello_class=class_create(THIS_MODULE,"hello");//创建类
    int i;
    for(i=0;i<3;i++)
    {   //自动创建设备
       hello_class_dev[i]=device_create(hello_class,NULL,MKDEV(major,i),NULL,"hello%d",i);
    }
    return 0;
}

static void hello_exit(void)
{
    cdev_del(&hello_cdev);
    unregister_chrdev_region(MKDEV(major,0),2);
    int i;
    for(i=0;i<3;i++)
    {
        class_device_destroy(hello_class, MKDEV(major, i));
    }
    class_destroy(hello_class);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

应用程序很简单:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
    int fd=open(argv[1],O_RDWR);
    if(-1==fd)
    {
        printf("Can‘t open!\n");
        return ;
    }
    printf("Open OK!\n");
    return 0;
}

Android音频驱动-ASOC之创建设备节点
从此可以看出,现在只有(252,0)和(252,1)对应了驱动程序中的file_operations结构体,而(252,2)虽然也是一个存在的设备文件,
但是由于驱动程序中没有它对应的file_operations结构体,所以应用程序打开它的时候被拒绝了。
下面可以看几个class几个名字的对应关系:
Android音频驱动-ASOC之创建设备节点

相关标签: android