android内核字符驱动设备实战之设备驱动程序篇
一. 进入到kernel/goldfish/drivers目录,新建testdev目录:
       ~/Android$ cd kernel/goldfish/drivers
        ~/Android/kernel/goldfish/drivers$ mkdir testdev
二. 在hello目录中增加testdev.h文件:
//条件指示符#ifndef...#endif 最主要目的是防止头文件的重复包含和编译
     #ifndef _TESTDEV_ANDROID_H_  
     #define _TESTDEV_ANDROID_H_     
     #include <linux/cdev.h>     
     #define TEST_DEVICE_NODE_NAME  "testdev"  
     #define TEST_DEVICE_FILE_NAME  "testdev"  
     #define TEST_DEVICE_CLASS_NAME "testdev"   
     //虚拟的硬件设备的,字符设备结构体
     struct test_android_dev
     {  
         int val; //设备要操作的成员变量 
         struct cdev dev;  //内嵌标准的字符设备结构体,自定义字符设备驱动必须包含该结构
     };        
     #endif  三.在testdev目录中增加testdev.c文件
//需要包含的头文件
 #include <linux/init.h>  
 #include <linux/module.h>  
 #include <linux/types.h>  
 #include <linux/fs.h>   
 #include <linux/device.h>  
 #include <asm/uaccess.h>  
  #include "testdev.h"  
 //定义主设备号和从设备号
 static int testdev_major = 0;
 static int testdev_minor = 0;
 //定义设备结构体变量
 static struct test_android_dev* test_dev = NULL;
 static struct class* testdev_class = NULL; 
 //定义标准的设备文件操作方法 ,返回值和参数必须按这个标准定义 
 static int testdev_open(struct inode* inode, struct file* filp);  
 static int testdev_release(struct inode* inode, struct file* filp);  
 static ssize_t testdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);  
 static ssize_t testdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);  
  //设备文件操作结构
 static struct file_operations testdev_fops = {  
     .owner = THIS_MODULE,  
     .open  =  testdev_open,  
     .release = testdev_release,  
     .read = testdev_read,  
     .write = testdev_write,   
 };  
 /*-----------start---设备文件操作函数的实现------------*/
 //打开设备
 static int testdev_open(struct inode* inode, struct file* filp) 
 {  
     struct test_android_dev* dev;          
     //根据该设备的信息节点inode,获取设备结构体的指针 
     dev = container_of(inode->i_cdev, struct test_android_dev, dev); 
     //将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用
     // 因为设备读写操作函数只有文件参数file,而没有信息节点参数 
     filp->private_data = dev;  
       return 0;  
 }  
       
 //释放设备,空实现,真实设备需要的话,可以在此做最后的清理工作
 static int testdev_release(struct inode* inode, struct file* filp)
 {  
     return 0;  
 }  
  //设备读取操作,返回值为读取的大小,如果为0则读取失败 
 static ssize_t testdev_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos)
 {  
     ssize_t ret = 0;  
     //获取设备结构体指针
     struct test_android_dev* dev = filp->private_data;          
   //如果用户空间的BUFF小于设备变量的值 ,则退出
     if(count < sizeof(dev->val))
     {  
         goto out;  
     }            
     //将内核空间设备变量的值拷贝到用户空间的BUF
     if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) 
     {  
        ret = -EFAULT;  
        goto out;  
     }  
     ret = sizeof(dev->val);      
     out:  
         return ret;  
 }  
       
 //设备写入操作,把用户空间的值写入到设备
 static ssize_t testdev_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
 {  
     ssize_t ret = 0;  
    //获取设备结构体指针
    struct test_android_dev* dev = filp->private_data;             
    //如果用户空间BUF的大小同设备的不同,则退出  
    if(count != sizeof(dev->val))
    {  
         goto out;          
    }                
    //将用户空间的数据拷贝到设备中去
    if(copy_from_user(&(dev->val), buf, count))
    {  
        ret = -EFAULT;  
        goto out;  
    }    
    ret = sizeof(dev->val);      
    out:  
         return ret;  
  }  
 /*------------end---设备文件操作函数的实现------------*/

 //设备初始化注册函数,有模块加载入口函数调用 
 static int  __testdev_setup_dev(struct test_android_dev* dev)
 {  
     int err;  
     //根据设备的主设备号和从设备号生成设备编号
     dev_t devno = MKDEV(testdev_major, testdev_minor);  
     memset(dev, 0, sizeof(struct test_android_dev));  
     //设备初始化,向系统注册该设备的操作函数表
     cdev_init(&(dev->dev), &testdev_fops);  
     dev->dev.owner = THIS_MODULE;  
     dev->dev.ops = &testdev_fops;  
     dev->val = 0;          
     //添加设备到系统,激活设备
     err = cdev_add(&(dev->dev),devno, 1);  
     if(err) 
     {  
         return err;  
     }         
     return 0;  
 } 

 //模块加载入口函数
 static int __init testdev_init(void)
 {   
     int err = -1;  
     dev_t dev = 0;  
     printk(KERN_ALERT"Initializing testdev device.\n");            
     //动态分配主设备和从设备号 
     err = alloc_chrdev_region(&dev, 0, 1, TEST_DEVICE_NODE_NAME);  
     if(err < 0) 
     {  
         printk(KERN_ALERT"Failed to alloc char dev region.\n");  
         goto fail;  
     }  
     //根据设备编号获取主设备号和从设备号
     testdev_major = MAJOR(dev);  
     testdev_minor = MINOR(dev);            
     //给设备结构体分配空间
     test_dev = kmalloc(sizeof(struct test_android_dev), GFP_KERNEL);  
     if(!test_dev) 
     {  
         err = -ENOMEM;  
         printk(KERN_ALERT"Failed to alloc test_dev.\n");  
         goto unregister;  
     }          
     /*初始化设备*/  
     err = __testdev_setup_dev(test_dev);  
     if(err) {  
         printk(KERN_ALERT"Failed to setup dev: %d.\n", err);  
         goto cleanup;  
     }          
    //*****模拟器测试很关键----注册一个类,使mdev可以在"/dev/"目录下 面建立设备节点
     testdev_class = class_create(THIS_MODULE, TEST_DEVICE_FILE_NAME); 
     //*****模拟器测试很关键--创建一个设备节点,节点名为TEST_DEVICE_FILE_NAME
     device_create(testdev_class, NULL, dev, "%s", TEST_DEVICE_FILE_NAME); 
     printk(KERN_ALERT"Succedded to initialize testdev device.\n");  
     return 0;  
 cleanup:  
     kfree(test_dev);  
 unregister:  
     unregister_chrdev_region(MKDEV(testdev_major, testdev_minor), 1);  
 fail:  
     return err;  
 }   

 //模块卸载函数
 static void __exit testdev_exit(void)
  {  
     dev_t devno = MKDEV(testdev_major, testdev_minor);    
     printk(KERN_ALERT"Destroy testdev device.\n");          
     if(test_dev) 
     {   //注销设备
         cdev_del(&(test_dev->dev));  
         //释放设备内存
         kfree(test_dev);  
     }          
     //释放设备号
     unregister_chrdev_region(devno, 1);  
 } 

 //模块必须通过MODULE_LICENSE宏声明此模块的许可证,否则在加载此模块时,会收到内核被污染的警告
 MODULE_LICENSE("GPL");  
 MODULE_DESCRIPTION("Test Android Driver");  

 //声明模块的初始化入口函数和退出函数 
 module_init(testdev_init);  
 module_exit(testdev_exit);  

四.在testdev录中新增Kconfig和Makefile两个文件内核模块或驱动编译时,在模块目录内必须有这两个文件。
分布到各目录的Kconfig构成了一个分布式的内核配置数据库,每个Kconfig分别描述了所属目录源文档相关的内核配置菜单。
Kconfig文件的内容
config TESTDEV   #配置的菜单项为TESTDEV
 
支持以模块、内建和不编译三种编译方法。后面是模块的提示字符
 
  default n #默认值:y-内建 m-模块,n-不编译;此处表示:在编译配置时,如果用户没有选择,则先择不编译
 
  help #help相当于注释一样,在给编辑Kconfig文件的人看的,这样可以保持其可读性,下面为注释的内容
 
 This is the test android driver.
 
 
       
    
 
   注:模块的编译类型,有编译、还是不编译、以及编译成什么类型。类型可以是: 
 bool、tristate、string、hex和int。
 
     bool类型的只能选中或不选中,选中为y,不选中为n.
     tristate类型的菜单项为值可为三种值,多了编译成内核模块的选项。其值可为y,n,m.
           string类型表示需要用户输入一串字符串。
          hex类型则需要用户输入一个16进制数。
          int类型表示用户输入一个整型.
    Makefile文件的内容
 
 
        obj-$(CONFIG_TESTDEV) +=testdev.o
 
 
hello.o这个目标是要编译进内核,还是作为模块编译:
 
 
obj-y +=testdev.o
 
 
        obj-m+= testdev.o
 
 
obj-n += testdev.o //不编译
 
 
$(CONFIG_TESTDEV)决定,而它的只在内核模块编译配置时,有用户设置
 
 
五. 修改arch/arm/Kconfig和drivers/kconfig两个文件
  在menu "Device Drivers"和endmenu之间添加一行:
 
 
      source "drivers/testdev/Kconfig"   #菜单连接项,执行时需要连接该目录下的Kconfig文件 
 
 
 
 
           
 
 
 
六. 修改drivers/Makefile文件
 
 

          添加如下内容: 
 
 
 

            obj-$(CONFIG_TESTDEV) += testdev/ 
 
 
 
 
       表示testdev目录是否编译,当CONFIG_TESTDEV为y或者m的时候,会去找testdev目录下的Makefile文件 
 
 
 
七. 配置编译选项:
 
 

            
  /Android/kernel/goldfish$ make menuconfig 
 
 
 

           找到"Device Drivers" => "Test Android Drivers"选项,设置为y. 
 
 
 

       
 
 
 

          
   配置编译选项的工作流程: 
 
 
 
 
         1、执行编译选项配置命令:make menuconfig 
 
 
 
 
  arch/arm/Kconfig和drivers/kconfig,读取主配置菜单。 
 
 
 
 
  Device Drivers”的菜单,其菜单中有一个 
 
 
 
 
             菜单连接项source "drivers/testdev/Kconfig"  
 
 
 
 
       4、根据菜单连接项的指示,把testdev这个目录里的Kconfig文件连接进来。 
 
 
 
 
       5、根据testdev目录里Kconfig的内容,使在配置编译选项的配置界面中, 
 
 
 
 
            设备驱动菜单"Device Drivers"下,包含"Test Android Drivers"这个菜单项, 
 
 
 
 
            在此选择y,表示把testdev驱动编译进内核。 
 
 
 
 
       6、在配置完成后,goldfish目录下生成一个.config文件,这是个隐藏文件, 
 
 
 
 
            这个文件记录着各个选项的配置及值。供Makefile文件使用. 
 
 
 
 
八. 编译:
 
 

            
  /Android/kernel/goldfish$ make 
 
 
 
 
          编译成功后,就可以在testdev目录下看到testdev.o文件了,这时候编译出来的zImage已经包含了testdev驱动。 
 
 
 

          
 
 
 

        
  编译的工作流程: 
 
 
 
 
         1、执行编译命令:make 
 
 
 
 
         2、读取内核配置文档".config",可知CONFIG_TESTDEV值为y 
 
 
 
 
  执行drivers/Makefile->其内容 
 
 
 
 
            有:obj-$(CONFIG_TESTDEV) += testdev/这一项。 
 
 
 
 
       4、由于CONFIG_TESTDEV值为y,则编译testdev目录下内容, 
 
 
 
 
               调用testdev目录下编译文件Makefile 
 
 
 
 
  obj-$(CONFIG_TESTDEV) +=testdev.o:由于CONFIG_TESTDEV值为y, 
 
 
 
 
  testdev.o编译进内核。 
 
 
 
九. 运行新编译的内核文件,验证testdev驱动程序是否已经正常安装: 
  

             
   /Android$ emulator -kernel ./kernel/goldfish/arch/arm/boot/zImage & 
  
 
  
 
           /Android$ adb shell 
  
 
  
 
    
   
进入到dev目录,可以看到testdev设备文件:
 
   

             root@android:/ # cd dev 
   
 
   

             root@android:/dev # ls