三种方式注册一个字符设备(按键驱动完整程序及测试程序)【转载】

原文地址:http://blog.163.com/hefeng330467115@126/blog/static/78205842201072932412573/

1. register_chrdev注册指定主次设备号的设备,手动在/dev下创建该设备的设备节点

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

#define DEVICE_NAME     "buttons"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define BUTTON_MAJOR    232         /* 主设备号 */

struct button_irq_desc {
    int irq;
    unsigned long flags;
    char *name;
};

/* 用来指定按键所用的外部中断引脚及中断触发方式, 名字 */
static struct button_irq_desc button_irqs [] = {
    {IRQ_EINT1, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */
    {IRQ_EINT4, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */
    {IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */
    {IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */
};

/* 按键被按下的次数(准确地说,是发生中断的次数) */
static volatile int press_cnt [] = {0, 0, 0, 0};

/* 等待队列:
 * 当没有按键被按下时,如果有进程调用s3c24xx_buttons_read函数,
 * 它将休眠
 */

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;


static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    volatile int *press_cnt = (volatile int *)dev_id;
   
    *press_cnt = *press_cnt + 1; /* 按键计数加1 */
    ev_press = 1;                /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
   
    return IRQ_RETVAL(IRQ_HANDLED);
}
/* 应用程序对设备文件/dev/buttons执行open(...)时,
 * 就会调用s3c24xx_buttons_open函数
 */

static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
    int i;
    int err;
   
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
        // 注册中断处理函数
        err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
                          button_irqs[i].name, (void *)&press_cnt[i]);
        if (err)
            break;
    }

    if (err) {
        // 释放已经注册的中断
        i--;
        for (; i >= 0; i--)
            free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
        return -EBUSY;
    }
   
    return 0;
}


/* 应用程序对设备文件/dev/buttons执行close(...)时,
 * 就会调用s3c24xx_buttons_close函数
 */

static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
    int i;
   
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
        // 释放已经注册的中断
        free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
    }

    return 0;
}
/* 应用程序对设备文件/dev/buttons执行read(...)时,
 * 就会调用s3c24xx_buttons_read函数
 */

static int s3c24xx_buttons_read(struct file *filp, char __user *buff,
                                         size_t count, loff_t *offp)
{
    unsigned long err;
   
    /* 如果ev_press等于0,休眠 */
    wait_event_interruptible(button_waitq, ev_press);

    /* 执行到这里时,ev_press等于1,将它清0 */
    ev_press = 0;

    /* 将按键状态复制给用户,并清0 */
    err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));
    memset((void *)press_cnt, 0, sizeof(press_cnt));

    return err ? -EFAULT : 0;
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中的对应函数
 */

static struct file_operations s3c24xx_buttons_fops = {
    .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
    .open    =   s3c24xx_buttons_open,
    .release =   s3c24xx_buttons_close,
    .read    =   s3c24xx_buttons_read,
};

/*
 * 执行“insmod s3c24xx_buttons.ko”命令时就会调用这个函数
 */

static int __init s3c24xx_buttons_init(void)
{
    int ret;

    /* 注册字符设备驱动程序
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数
     * BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号
     */
    ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }
   
    printk(DEVICE_NAME " initialized\n");
    return 0;
}

/*
 * 执行”rmmod s3c24xx_buttons.ko”命令时就会调用这个函数
 */
static void __exit s3c24xx_buttons_exit(void)
{

    /* 卸载驱动程序 */
    unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");            

MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");  

MODULE_LICENSE("GPL");                               

2. register_chrdev_region 注册一个指定主次设备号的设备,后利用class类在/dev/目录下自动创建一个该设备的节点

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/cdev.h>
#include <linux/device.h>


#define DEVICE_NAME     "buttons"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define BUTTON_MAJOR    233       /* 主设备号 */
#define BUTTON_MINOR     0     //次设备号
#define NUMBER_OF_DEVICES  1   //要注册设备的个数
struct cdev cdev;   //定义一个cdev结构的设备
dev_t dev = 0;

struct button_irq_desc {
    int irq;
    unsigned long flags;
    char *name;
};

/* 用来指定按键所用的外部中断引脚及中断触发方式, 名字 */
static struct button_irq_desc button_irqs [] = {
    {IRQ_EINT1, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */
    {IRQ_EINT4, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */
    {IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */
    {IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */
};

/* 按键被按下的次数(准确地说,是发生中断的次数) */
static volatile int press_cnt [] = {0, 0, 0, 0};

/* 等待队列:
 * 当没有按键被按下时,如果有进程调用s3c24xx_buttons_read函数,
 * 它将休眠
 */

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    volatile int *press_cnt = (volatile int *)dev_id;
    *press_cnt = *press_cnt + 1; 
    ev_press = 1;             
    wake_up_interruptible(&button_waitq);   
    return IRQ_RETVAL(IRQ_HANDLED);
}
/* 应用程序对设备文件/dev/buttons执行open(...)时,
 * 就会调用s3c24xx_buttons_open函数
 */

static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
    int i;
    int err;
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
             err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
                          button_irqs[i].name, (void *)&press_cnt[i]);
        if (err)
            break;
    }

    if (err) {
        i--;
        for (; i >= 0; i--)
            free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
        return -EBUSY;
    }    
    return 0;
}
/* 应用程序对设备文件/dev/buttons执行close(...)时,
 * 就会调用s3c24xx_buttons_close函数
 */

static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
    int i;   
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
        free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
    }

    return 0;
}
/* 应用程序对设备文件/dev/buttons执行read(...)时,
 * 就会调用s3c24xx_buttons_read函数
 */

static int s3c24xx_buttons_read(struct file *filp, char __user *buff,
                                         size_t count, loff_t *offp)
{
    unsigned long err;
    /* 如果ev_press等于0,休眠 */
    wait_event_interruptible(button_waitq, ev_press);

    /* 执行到这里时,ev_press等于1,将它清0 */
    ev_press = 0;

    /* 将按键状态复制给用户,并清0 */
    err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));
    memset((void *)press_cnt, 0, sizeof(press_cnt));

    return err ? -EFAULT : 0;
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中的对应函数
 */

static struct file_operations s3c24xx_buttons_fops = {
    .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
    .open    =   s3c24xx_buttons_open,
    .release =   s3c24xx_buttons_close,
    .read    =   s3c24xx_buttons_read,
};

/*向内核添加设备的函数*/
static void char_reg_setup_cdev (void)   
 
  {
     int error;
     dev_t devno = MKDEV (BUTTON_MAJOR,BUTTON_MINOR);    //将已有的主次设备号转换为一个 dev_t, 
/*初始化设备cdev同时建立和_fops结构的联系*/
      cdev_init (&cdev, &s3c24xx_buttons_fops);
      cdev.owner = THIS_MODULE;
      cdev.ops = &s3c24xx_buttons_fops;  
/*添加设备   如果是动态申请的设备号,就用 
      cdev_add(&cdev,MKDEV(mem_major, 0), MEMDEV_NR_DEVS);                                  
    向内核添加设备,第一个参数是设备,第二个参数是设备号,
    第三个参数是要注册的次设备数目,mem_major在动态申请时候保存起来了。
*/
       error = cdev_add (&cdev, devno , 1);
       if (error)
       printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
}
struct class *my_class; //定义一个类,为在/dev目录下自动创建设备节点

/*
 * 执行“insmod s3c24xx_buttons.ko”命令时就会调用这个函数
 */

static int __init s3c24xx_buttons_init(void)
{
    int result;
/*1. 注册获取字符设备号*/
   dev = MKDEV (BUTTON_MAJOR,BUTTON_MINOR)
   result = register_chrdev_region (dev, NUMBER_OF_DEVICES, DEVICE_NAME);
   if (result<0)
    {
     printk (KERN_WARNING "hello: can't get major number %d\n", BUTTON_MAJOR);
     return result;
    }
/*2. 利用注册的设备号向内核添加设备*/
   char_reg_setup_cdev ();
/*3. 在/sysfs下创建一个类 */
 my_class = class_create(THIS_MODULE, "my_class");
 if(IS_ERR(my_class))
 {
   printk("Err: failed in creating class.\n");
   return -1;
  }
 /*4. 在sysfs中注册你的驱动,且利用已经创建好的类在/dev/目录下创建一个该设备的节点 */
  device_create( my_class, NULL, MKDEV(BUTTON_MAJOR, 0), DEVICE_NAME);
   printk (KERN_INFO "Registered character driver\n");
  return 0;
}

/*
 * 执行”rmmod s3c24xx_buttons.ko”命令时就会调用这个函数
 */

static void __exit s3c24xx_buttons_exit(void)
{
    /* 卸载驱动程序 */

 dev_t devno = MKDEV(BUTTON_MAJOR,BUTTON_MINOR);
 cdev_del (&cdev); //删除设备结构
 device_destroy(my_class, MKDEV(BUTTON_MAJOR, 0)); //删除 /dev目录下的节点
 class_destroy(my_class); //删除创建的class
 unregister_chrdev_region (devno, NUMBER_OF_DEVICES); //注销字符设备
 printk (KERN_INFO "char driver cleaned up\n");
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");           

MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");  

MODULE_LICENSE("GPL");                            

3. register_chrdev_region 注册一个系统自动分配主次设备号的设备,后利用class类在/dev/目录下自动创建一个该设备的节点

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/cdev.h>
#include <linux/device.h>


#define DEVICE_NAME     "buttons"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */

int button_major;         /* 主设备号 */
int button_minor=0;     //此设备号
int number_of_devices=1;   //要注册设备的个数
struct cdev cdev;   //定义一个cdev结构的设备
dev_t dev = 0;

struct button_irq_desc {
    int irq;
    unsigned long flags;
    char *name;
};

/* 用来指定按键所用的外部中断引脚及中断触发方式, 名字 */
static struct button_irq_desc button_irqs [] = {
    {IRQ_EINT1, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */
    {IRQ_EINT4, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */
    {IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */
    {IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */
};

/* 按键被按下的次数(准确地说,是发生中断的次数) */
static volatile int press_cnt [] = {0, 0, 0, 0};

/* 等待队列:
 * 当没有按键被按下时,如果有进程调用s3c24xx_buttons_read函数,
 * 它将休眠
 */

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    volatile int *press_cnt = (volatile int *)dev_id;
   
    *press_cnt = *press_cnt + 1; 
    ev_press = 1;             
    wake_up_interruptible(&button_waitq);  
    
    return IRQ_RETVAL(IRQ_HANDLED);
}
/* 应用程序对设备文件/dev/buttons执行open(...)时,
 * 就会调用s3c24xx_buttons_open函数
 */

static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
    int i;
    int err; 
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
         err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
                          button_irqs[i].name, (void *)&press_cnt[i]);
        if (err)
            break;
    }

    if (err) {
        i--;
        for (; i >= 0; i--)
            free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
        return -EBUSY;
    }
   
    return 0;
}
/* 应用程序对设备文件/dev/buttons执行close(...)时,
 * 就会调用s3c24xx_buttons_close函数
 */

static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
    int i;
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
          free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
    }

    return 0;
}
/* 应用程序对设备文件/dev/buttons执行read(...)时,
 * 就会调用s3c24xx_buttons_read函数
 */

static int s3c24xx_buttons_read(struct file *filp, char __user *buff,
                                         size_t count, loff_t *offp)
{
    unsigned long err;
    wait_event_interruptible(button_waitq, ev_press);
    ev_press = 0;
    err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));
    memset((void *)press_cnt, 0, sizeof(press_cnt));

    return err ? -EFAULT : 0;
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中的对应函数
 */

static struct file_operations s3c24xx_buttons_fops = {
    .owner   =   THIS_MODULE,   

    .open    =   s3c24xx_buttons_open,
    .release =   s3c24xx_buttons_close,
    .read    =   s3c24xx_buttons_read,
};

/*向内核添加设备的函数*/
static void char_reg_setup_cdev (void)   
    {
       int error;
    dev_t devno = MKDEV (button_major,button_minor);  //将已有的主次设备号转换为一个 dev_t,      
 /*初始化设备cdev同时建立和_fops结构的联系*/
    cdev_init (&cdev, &s3c24xx_buttons_fops);
       cdev.owner = THIS_MODULE;
       cdev.ops = &s3c24xx_buttons_fops;
   
/*添加设备   如果是动态申请的设备号,就用
cdev_add(&cdev,MKDEV(mem_major, 0), MEMDEV_NR_DEVS);                                  
    向内核添加设备,第一个参数是设备,第二个参数是设备号,
    第三个参数是要注册的次设备数目,mem_major在动态申请时候保存起来了。
*/

    //error = cdev_add(&cdev,MKDEV(button_major, 0), number_of_devices)
    error = cdev_add (&cdev, devno ,number_of_devices);
       if (error)
           printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
}
struct class *my_class;
//定义一个类,为在/dev目录下自动创建设备节点

/*
 * 执行“insmod s3c24xx_buttons.ko”命令时就会调用这个函数
 */

static int __init s3c24xx_buttons_init(void)
{

    int result;
/*1. 注册获取字符设备号动态分配*/
  result=alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);//动态获得主设备号
  if (result<0)
  {
    printk (KERN_WARNING "hello: can't auto get major number\n");
    return result;
   }
  button_major=MAJOR(dev);
  
/*2. 利用注册的设备号向内核添加设备*/
   char_reg_setup_cdev ();
/*3. 在/sysfs下创建一个类 */
 my_class = class_create(THIS_MODULE, "my_class");
 if(IS_ERR(my_class))
 {
   printk("Err: failed in creating class.\n");
   return -1;
  }
 /*4. 在sysfs中注册你的驱动,且利用已经创建好的类在/dev/目录下创建一个该设备的节点 */
  device_create( my_class, NULL, MKDEV(button_major, button_minor), DEVICE_NAME);
   printk (KERN_INFO "Registered character driver\n");
  return 0;
}

/*
 * 执行”rmmod s3c24xx_buttons.ko”命令时就会调用这个函数
 */

static void __exit s3c24xx_buttons_exit(void)
{

    /* 卸载驱动程序 */

 dev_t devno = MKDEV(button_major,button_minor);
 cdev_del (&cdev); //删除设备结构
 device_destroy(my_class, MKDEV(button_major,button_minor)); //删除 /dev目录下的节点
 class_destroy(my_class); //删除创建的class
 unregister_chrdev_region (devno, number_of_devices);  //注销字符设备
 printk (KERN_INFO "char driver cleaned up\n");
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");            

MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");  

MODULE_LICENSE("GPL");   

4.测试程序             

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(int argc, char **argv)
{
    int i;
    int ret;
    int fd;
    int press_cnt[4];
   
    fd = open("/dev/buttons", 0);  // 打开设备
    if (fd < 0) {
        printf("Can't open /dev/buttons\n");
        return -1;
    }

    // 这是个无限循环,进程有可能在read函数中休眠,当有按键被按下时,它才返回
    while (1) {
        // 读出按键被按下的次数
        ret = read(fd, press_cnt, sizeof(press_cnt));
        if (ret < 0) {
            printf("read err!\n");
            continue;
        }

        for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {
            // 如果被按下的次数不为0,打印出来
            if (press_cnt[i])
                printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]);
        }
    }
   
    close(fd);
    return 0;   
}

posted @ 2015-09-24 16:00  Kconfig  阅读(458)  评论(0编辑  收藏  举报