基于ubuntu14.04 Linux内核驱动的编写

来源:互联网 发布:vmware mac 破解版 编辑:程序博客网 时间:2024/06/11 09:25
基于ubuntu14.04 Linux内核驱动的编写

    前面已经总结了关于安卓源码及Linux源码的编译,并且成功启动了模拟器,那么接下来就正式的编写安卓底层驱动了。在编写之前,我们应该先了解下Linux驱动编写的步骤。

    编写Linux驱动,主要是构建Linux驱动的框架,框架搭好了,整个驱动的编写工作也就完成一大半了。   

    Step 1:申请设备号(主要是申请主设备号)
           两种方式:(1)静态申请 函数

 intregister_chrdev_region(dev_t from,unsigned count, const char *name);

/ * register_chrdev_region() - register arange of device numbers

 * @from: the first in the desired range of devicenumbers; must include

 *       the major number.

 * @count: the number of consecutive device numbersrequired

 * @name: the name of the device or driver.

 *Return value is zero on success, a negative error code on failure.*/

   这种方式主要用于,驱动开发者事先知道该驱动主设备号的情况

                      (2)动态申请

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,     const char *name)

/* alloc_chrdev_region() - register a rangeof char device numbers

 * @dev: output parameter for first assigned number

 * @baseminor: first of the requested range of minornumbers

 * @count: the number of minor numbers required

 * @name: the name of the associated device or driver

 *

 *Allocates a range of char device numbers. The major number will be

 *chosen dynamically, and returned (along with the first minor number)

 * in@dev. Returns zero or a negative errorcode.*/

这种方式由系统动态分配一个设备号,返回的设备号保存在参数dev中。


    Step 2:注册字符设备

           在Linux内核中庸struct cdev表示一个字符设备。

           字符设备的注册与注销分别通过下面的两个函数来实现:

          

int cdev_add(structcdev *p, dev_t dev, unsigned count)

/**

 *cdev_add() - add a char device to the system

 *@p: the cdev structure for the device

 *@dev: the first device number for which this device is responsible

 *@count: the number of consecutive minor numbers corresponding to this

 *        device

 *

 *cdev_add() adds the device represented by @p to the system, making it

 *live immediately. A negative error codeis returned on failure.

 */

void cdev_del(structcdev *p)

 

不过,在注册一个字符设备之前,要调用下面这个函数来初始化struct cdev结构体:

void cdev_init(structcdev *cdev,const struct file_operations *fops)

/**

 *cdev_init() - initialize a cdev structure

 *@cdev: the structure to initialize

 *@fops: the file_operations for this device

 *

 *Initializes @cdev, remembering @fops, making it ready to add to the

 *system with cdev_add().

 */

 

另外,struct cdev结构体变量可以声明为一个指针,内核提供了一个函数来申请:

struct cdev *cdev_alloc(void)


    Step 3:创建设备节点

          

有两种方法:

一是通过 mknod命令来创建。如:

  mknod /dev/yourname c major minor

其中yourname”可以是任意符合unix下路径名的名字,不一定要是你代码里定义的驱动或设备的名字;c表示创建字符设备节点,major是你成功申请的主设备号,minor是次设备号,这个可以是任意的(在次设备号范围内)

 

另外一种方法是通过udev自动生成。这种方法需要在你的代码里创建一个设备类,然后在这个设备类的基础上,创建一个设备;另外应用程序需要跑一个udevd的后台程序。

struct class* class_create(owner, name);

struct device *device_create(struct class *class, struct device *parent,

   dev_t devt, void *drvdata,const char *fmt, ...)


    这样Linux驱动编写的一般步骤就完成了,我们阅读Linux内核驱动的代码,应该先找到程序的入口函数module_init(char_test_init);参数中的就是函数入口,函数名可以自己定义,从入口进入,根据以上的步骤阅读代码,那么Linux内核驱动的框架就显得简单明了了。


    接下来以LED驱动为例子,阅读下LED驱动的代码。



#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/device.h>#include <asm/uaccess.h> #include <asm/string.h>#include <asm/uaccess.h>//kmalloc函数头文#include <linux/slab.h>#include <linux/mutex.h>//驱动头文件#include <mach/gpio.h>        /*  \arch\arm\mach-s5pv210\include\  */#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>    /*  \arch\arm\plat-samsung\include\  */#define LED_ON _IOW('a',2,int)#define LED_OFF _IOW('a',3,int)#define LED_ALL_ON _IO('a',0xF)#define LED_ALL_OFF _IO('a',0)#define LED_LIUSHUI_ON _IO('a',98)#define LED_LIUSHUI_OFF _IO('a',99)MODULE_LICENSE("GPL");int devno_major=0;int devno_minor=0;int init_gpio_led(void){if(!gpio_request(S5PV210_GPJ2(0), "led_1")){return -1;}if(!gpio_request(S5PV210_GPJ2(1), "led_2")){return -1;}if(!gpio_request(S5PV210_GPJ2(2), "led_3")){return -1;}if(!gpio_request(S5PV210_GPJ2(3), "led_4")){return -1;}gpio_direction_output(S5PV210_GPJ2(0), 1);gpio_direction_output(S5PV210_GPJ2(1), 1);gpio_direction_output(S5PV210_GPJ2(2), 1);gpio_direction_output(S5PV210_GPJ2(3), 1);return 0;}module_param(devno_major, int, 0440);struct cdev *pdev=NULL;struct class * myclass  = NULL; struct device *mdevice = NULL;int test_open(struct inode *_inode,struct file *_file){printk(KERN_INFO "%s\n", __FUNCTION__);return 0;}int test_close(struct inode *_inode,struct file *_file){printk(KERN_INFO "%s\n", __FUNCTION__);return 0;}ssize_t  test_read (struct file *_file, char __user * buf, size_t count, loff_t * offset){return 0;}ssize_t  test_write (struct file *_file, const char __user * buf, size_t count, loff_t * offset){return 0;}long test_ioctl (struct file * _file, unsigned int cmd, unsigned long arg){int *args=(int *)arg;int k;if (_IOC_DIR(cmd) == _IOC_READ) //该命令,是用户想从内核读一个数据{//我就必须要验证你提供的地址,是否可写if (!access_ok(VERIFY_WRITE, arg, _IOC_SIZE(cmd)) ){return -EFAULT;}} else if (_IOC_DIR(cmd) == _IOC_WRITE){if (!access_ok(VERIFY_READ, arg, _IOC_SIZE(cmd))){return -EFAULT;}}get_user(k,args);switch(cmd){case LED_ON:__gpio_set_value(S5PV210_GPJ2(k),0);break;case LED_OFF:__gpio_set_value(S5PV210_GPJ2(k),1);break;case LED_ALL_ON:__gpio_set_value(S5PV210_GPJ2(0),0);__gpio_set_value(S5PV210_GPJ2(1),0);__gpio_set_value(S5PV210_GPJ2(2),0);__gpio_set_value(S5PV210_GPJ2(3),0);break;case LED_ALL_OFF:__gpio_set_value(S5PV210_GPJ2(0),1);__gpio_set_value(S5PV210_GPJ2(1),1);__gpio_set_value(S5PV210_GPJ2(2),1);__gpio_set_value(S5PV210_GPJ2(3),1);break;case LED_LIUSHUI_ON :__gpio_set_value(S5PV210_GPJ2(0),1);__gpio_set_value(S5PV210_GPJ2(0),0);__gpio_set_value(S5PV210_GPJ2(1),1);__gpio_set_value(S5PV210_GPJ2(1),0);__gpio_set_value(S5PV210_GPJ2(2),1);__gpio_set_value(S5PV210_GPJ2(2),0);__gpio_set_value(S5PV210_GPJ2(3),1);__gpio_set_value(S5PV210_GPJ2(3),0);break;case LED_LIUSHUI_OFF :break;default:break;}return -1;}const struct file_operations fops=        //传统的字符设备访问方式 这里我偷懒只用ioctl实现对硬件的访问{.open =test_open,.release=test_close,.read=test_read,.write=test_write,.unlocked_ioctl = test_ioctl,};int  char_test_init(void){ int r,res;dev_t devno;   //32位数,其中的12位用来表示主设备号,其余的20位表示次设备号//申请设备号if(devno_major>0)//静态指定{devno =MKDEV(devno_major,devno_minor);r=register_chrdev_region(devno,1,"test");}else//动态申请{r=alloc_chrdev_region(&devno, devno_minor, 1, "test");}if(r!=0){printk(KERN_ERR "register char dev number failed\n");return -1;}devno_major=MAJOR(devno);    //获取主设备号devno_minor=MINOR(devno);    //获取次设备号printk(KERN_INFO"major: %d minor: %d\n", devno_major, devno_minor);//注册字符设备pdev =cdev_alloc();cdev_init(pdev,&fops);   //字符设备初始化cdev_add(pdev,devno,1);  //通过此函数告诉内核该结构的信息//生成设备节点myclass=class_create(THIS_MODULE, "char_test");mdevice=device_create(myclass,NULL,devno,NULL,"test");res=init_gpio_led();//初始化端口if(res!=0)return -1;return 0;}void char_test_exit(void){dev_t devno=MKDEV(devno_majkfree(pt->p_buf);//释放设备号device_destroy(myclass,devno);class_destroy(myclass);//注销字符设备cdev_del(pdev);gpio_free(S5PV210_GPJ2(0));gpio_free(S5PV210_GPJ2(1));gpio_free(S5PV210_GPJ2(2));gpio_free(S5PV210_GPJ2(3));unregister_chrdev_region(devno,1);}module_init(char_test_init);  //这里为函数入口module_exit(char_test_exit);  //这里为退出函数

    理解了上述代码后,那么如何将自己编写的驱动写进Linux内核呢?

    我们在linux内核中编写驱动,一般都是在/kernel/driver/ 下建立自己的目录,在新建的目录下创建C文件编写。比如说新建hello目录,在hello目录下新建hello.c及hello.h一些相关的头文件。驱动编写完成后,还需要配置Kconfig以及Makefile文件。以hello为列:

    其中Kconfig是在编译前执行配置命令make menuconfig时用到的,而Makefile是执行编译命令make是用到的:

    Kconfig文件的内容

       config HELLO

           tristate "First Android Driver"

           default n

           help

           This is the first android driver.


    

    Makefile文件的内容

      obj-$(CONFIG_HELLO) += hello.o

    

    在Kconfig文件中,tristate表示编译选项HELLO支持在编译内核时,hello模块支持以模块、内建和不编译三种编译方法,默认是不编译,因此,在编译内核前,我们还需要执行make menuconfig命令来配置编译选项,使得hello可以以模块或者内建的方法进行编译。

      在Makefile文件中,根据选项HELLO的值,执行不同的编译方法

      修改arch/arm/Kconfig和drivers/kconfig两个文件,在menu "Device Drivers"和endmenu之间添加一行:

      source "drivers/hello/Kconfig"(有些源码在arch/arm/Kconfig中没有menu "Device Drivers"和endmenu,那是因为在drivers/kconfig已经包含。所以只需要在drivers/kconfig添加即可。)

        这样,执行make menuconfig时,就可以配置hello模块的编译选项了。 

        

        修改drivers/Makefile文件,添加一行:

        obj-$(CONFIG_HELLO) += hello/

        配置编译选项:

        /Android-5.0.2/kernel/common$ make menuconfig

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

    注意,如果内核不支持动态加载模块,这里不能选择m,虽然我们在Kconfig文件中配置了HELLO选项为tristate。要支持动态加载模块选项,必须要在配置菜单中选择Enable loadable module support选项;在支持动态卸载模块选项,必须要在Enable loadable module support菜单项中,选择Module unloading选项。 


     这些工作做完之后,就可以直接在/kernel目录下make了。

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


参考博客:http://blog.csdn.net/luoshengyang/article/details/6568411 罗老师  


1 0
原创粉丝点击