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幾個名字的對應關係: