1. 程式人生 > >Android音訊驅動-ASOC之建立裝置節點

Android音訊驅動-ASOC之建立裝置節點

建立裝置檔案的方法:
第一種是使用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;
}

這裡寫圖片描述
從此可以看出,現在只有(252,0)和(252,1)對應了驅動程式中的file_operations結構體,而(252,2)雖然也是一個存在的裝置檔案,
但是由於驅動程式中沒有它對應的file_operations結構體,所以應用程式開啟它的時候被拒絕了。
下面可以看幾個class幾個名字的對應關係:
這裡寫圖片描述