现在的位置: 首页 > 综合 > 正文

android内核字符驱动设备实战之———-设备驱动程序篇

2018年06月10日 ⁄ 综合 ⁄ 共 7512字 ⁄ 字号 评论关闭

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

  tristate "Test Android Driver"  #tristate表示支持以模块、内建和不编译三种编译方法。后面是模块的提示字符
  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
       2、从主配置菜单arch/arm/Kconfig和drivers/kconfig,读取主配置菜单。
     3、在主配置菜单中,有一个名为“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
       3、执行根目录的Makefile文件-->调用执行drivers/Makefile->其内容
          有:obj-$(CONFIG_TESTDEV) += testdev/这一项。
     4、由于CONFIG_TESTDEV值为y,则编译testdev目录下内容,
             调用testdev目录下编译文件Makefile
     5、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
 

 

 

抱歉!评论已关闭.