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

ARM+LINUX字符型设备驱动编写框架

2013年09月17日 ⁄ 综合 ⁄ 共 6743字 ⁄ 字号 评论关闭

最近在ARM+LINUX嵌入式环境下学习字符型设备的驱动程序的编写,现整理如下:

字符型设备的编写其实还是有规律可循的,我自己总结了一个框图。

 驱动程序框架:

点击看大图

驱动程序里最重要的就是

module_init(XXX_init);module_exit(XXX_exit);

第一句话是告诉内核编写的这段程序是驱动程序,也就是将驱动程序以模块的形式注册进内核。后一句就是用于将驱动程序卸载掉。

 

接着就是编写static int __init XXX_init(void)函数,其中最重要的就是注册字符型设备,给它分配一个主设备号ret=register_chrdev(XXX_MAJOR,DEVICE_NAME,&XXX_fops);这个函数的第一个参数是注册的主设备号,可以自己指定,我这里就是采用外部宏定义的方式,也可以让系统自动分配,将该参数写0即可。分配的主设备号范围是0~254.第二个参数是自己指定的设备名,也就是将驱动加入内核后添加在/dev下的设备名,这个名字很重要,应用程序在调用open()函数时也要用到这个设备名。第三个就是一个结构体指针,我们的任务就是要丰满它。

static struct file operations XXX_fops =

 {

       .ioctl       =     XXX_ioctl,

};

实际上file operations这个结构体的内容非常多如下,但是使用时并不用全部使用。

       struct file_operations

 {   struct module *owner;

       loff_t (*llseek) (struct file *, loff_t, int);

        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

       int (*readdir) (struct file *, void *, filldir_t);

       unsigned int (*poll) (struct file *, struct poll_table_struct *);

       int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

       long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

       long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

       int (*mmap) (struct file *, struct vm_area_struct *);

       int (*open) (struct inode *, struct file *);

       int (*flush) (struct file *, fl_owner_t id);

       int (*release) (struct inode *, struct file *);

       int (*fsync) (struct file *, struct dentry *, int datasync);

     int (*aio_fsync) (struct kiocb *, int datasync);

       int (*fasync) (int, struct file *, int); ...

};

可以看到里面主要的一项就是.ioctl   =     XXX_ioctl,我们对硬件的一些操作就是在XXX_ioctl这个函数里面来实现的。

 

这些就是一个字符型设备驱动的主要内容,把以上这些函数的丰满一下,就可以组成一个完整的驱动程序。下面举例说明。

 

这是一个蜂鸣器的驱动。开发板是友善之臂的mini2440

下图是蜂鸣器部分的局部电路图,可以看到只要让GPB0口输出1就可以让蜂鸣器想起来。

蜂鸣器的驱动程序:

 

#include <linux/config.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/devfs_fs_kernel.h>

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

 

#define DEVICE_NAME     "beep"  //设备名beep

#define BEEP_MAJOR 253      //主设备号253

 

 

 

static int qq2440_beep_ioctl(

       struct inode *inode,

       struct file *file,

       unsigned int cmd,

       unsigned long arg)

{

      

             

              s3c2410_gpio_setpin(S3C2410_GPB0, cmd);

      

      

}

 

static struct file_operations qq2440_beep_fops = {

       .owner    =     THIS_MODULE,

       .ioctl       =     qq2440_beep_ioctl,

};

 

static int __init qq2440_beep_module_init(void)

{

       int ret;

       int i;

 

       ret = register_chrdev(BEEP_MAJOR, DEVICE_NAME, &qq2440_beep_fops);

       if (ret < 0) {

         printk(DEVICE_NAME " can't register major number/n");

         return ret;

       }

 

       devfs_mk_cdev(MKDEV(BEEP_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);

      

       s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_OUTP);

       s3c2410_gpio_setpin(S3C2410_GPB0, 0);

 

       printk(DEVICE_NAME " initialized/n");

       return 0;

}

 

static void __exit qq2440_beep_module_exit(void)

{

       devfs_remove(DEVICE_NAME);

       unregister_chrdev(BEEP_MAJOR, DEVICE_NAME);

       printk(DEVICE_NAME " removed/n");

}

 

module_init(qq2440_beep_module_init);

module_exit(qq2440_beep_module_exit);

 

可以看到这里只是将我之前框图的函数丰满了一下。

1. module_init(qq2440_beep_module_init);

module_exit(qq2440_beep_module_exit);

2. static int __init qq2440_beep_module_init(void)

注册设备,初始化硬件。

3. static void __exit qq2440_beep_module_exit(void)

卸载设备的函数。

4static struct file_operations qq2440_beep_fops =

编写file operations结构体。

5. tatic int qq2440_beep_ioctl

丰满ioctl函数。

注意一点,我的设备名是beep

 

接着我们就可以把该驱动程序放在kernel-2.6.23/drivers/char的目录下。并打开Kconfig文件,添加

Config QQ2440_BEEP_MODULE  //驱动程序文件名

              tristate”QQ2440_BEEP_MODULE”

              depends on ARCH_S3C2410

              help

                     qq2440_beep_module test

这样在内核根目录下make menuconfig后就可以把QQ2440_BEEP_MODULE显示在菜单上。把它前面的状态改为【M】,我们以模块的形式加载。

 

接着我们还需要修改Makefile文件。在里面添加

obj-$(CONFIG_QQ2440_BEEP_MODULE)+=qq2440_beep_module.o

然后我们在内核根目录下make modules时,就可以生成我们所需的qq2440_beep_module.ko文件了。把它放到开发板上,通过insmod加载,rmmod卸载。

 

下面是我的测试程序:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

 

int main(int argc, char **argv)

{

      

       int fd;

      

            fd = open("/dev/beep", 0); //可以看到,open函数里调用了设备名,这也是驱动程序和

                                                      //应用程序的连接点

       if (fd < 0) {

              perror("cannt open device beep");

              exit(1);

       }

       ioctl(fd,1,0);   //这里就是我们编写的ioctl函数的具体实现

       close(fd);

       return 0;

}

 

 

 

另外,再附上LED的驱动程序和测试程序,用于比较。

驱动程序:

#include <linux/config.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/init.h>

#include <linux/devfs_fs_kernel.h>

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <asm/arch/regs-gpio.h>

#include <asm/hardware.h>

 

#define DEVICE_NAME     "leds"

#define LED_MAJOR 231

 

static unsigned long led_table [] = {

       S3C2410_GPB5,

       S3C2410_GPB6,

       S3C2410_GPB7,

       S3C2410_GPB8,

};

 

static unsigned int led_cfg_table [] = {

       S3C2410_GPB5_OUTP,

       S3C2410_GPB6_OUTP,

       S3C2410_GPB7_OUTP,

       S3C2410_GPB8_OUTP,

};

 

static int qq2440_leds_ioctl(

       struct inode *inode,

       struct file *file,

       unsigned int cmd,

       unsigned long arg)

{

       switch(cmd) {

       case 0:

       case 1:

              if (arg > 4) {

                     return -EINVAL;

              }

              s3c2410_gpio_setpin(led_table[arg], !cmd);

              return 0;

       default:

              return -EINVAL;

       }

}

 

static struct file_operations qq2440_leds_fops = {

       .owner    =     THIS_MODULE,

       .ioctl       =     qq2440_leds_ioctl,

};

 

static int __init qq2440_leds_init(void)

{

       int ret;

       int i;

 

       ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &qq2440_leds_fops);

       if (ret < 0) {

         printk(DEVICE_NAME " can't register major number/n");

         return ret;

       }

 

       devfs_mk_cdev(MKDEV(LED_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);

      

       for (i = 0; i < 4; i++) {

              s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);

              s3c2410_gpio_setpin(led_table[i], 1);

       }

 

       printk(DEVICE_NAME " initialized/n");

       return 0;

}

 

static void __exit qq2440_leds_exit(void)

{

       devfs_remove(DEVICE_NAME);

       unregister_chrdev(LED_MAJOR, DEVICE_NAME);

}

 

module_init(qq2440_leds_init);

module_exit(qq2440_leds_exit);

 

测试程序:

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

 

int main(int argc, char **argv)

{

       int on;

       int led_no;

       int fd;

       if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||

           on < 0 || on > 1 || led_no < 0 || led_no > 3) {

              fprintf(stderr, "Usage: leds led_no 0|1/n");

              exit(1);

       }

       fd = open("/dev/leds0", 0);

       if (fd < 0) {

              perror("open device leds");

              exit(1);

       }

       ioctl(fd, on, led_no);

       close(fd);

       return 0;

}

【上篇】
【下篇】

抱歉!评论已关闭.