[TOC]


1. linuxfile cdev inode之间的关系;

struct file对象 : 描述进程中打开文件的信息,包括文件名、读写标志、文件的当前偏移指针等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <linux/fs.h>
struct file {
	/*
	 * fu_list becomes invalid after file_free is called and queued via
	 * fu_rcuhead for RCU freeing
	 */
	union {
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;   
	/* 略 */
	const struct file_operations	*f_op;  // 文件操作接口
	spinlock_t		f_lock;  /* f_ep_links, f_flags, no IRQ */
	/* 略 */
	loff_t			f_pos;   // 文件偏移
	struct fown_struct	f_owner;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;

	/* 略 */
	void			*private_data;  // 万能指针,可以用来存储私有的数据地址
	/* 略 */
};

struct cdev对象: 描述一个字符设备对象,任何一个字符设备驱动都应该有此对象;

1
2
3
4
5
6
7
8
9
#include <linux/cdev.h>
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;   // 设备号
	unsigned int count;
};

struct inode对象:描述文件系统中某个文件的信息;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <linux/fs.h>
struct inode {
	/* RCU path lookup touches following: */
	umode_t			i_mode;
	uid_t			i_uid;
	gid_t			i_gid;
	/*  省略众多暂时无关的成员 */
	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct file_lock	*i_flock;
	struct address_space	*i_mapping;
	struct address_space	i_data;
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS];
#endif
	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
	};
	/* 略 */
};

字符设备通过register_chrdev(设备号, fops)cdev结构体挂接在内核中cdev的链表上。

用户空间mknod的时候,会在/dev/下创建设备文件,该文件在内存中对应一个inode对象,mknod的设备号赋给inode.

用户空间open()的时候,通过inode中的i_cdev找到对应的cdev,将cdev->ops赋值给struct filef_op,执行f_op->open()

1
2
3
4
5
/* 通过inode获得主次设备号*/
int minor = iminot(const struct inode * inode);
int major = imajor(const struct inode * inode);
/* 通过filp找到inode */
struct inode * node = filp->f_path.dentry->d_inode;

2. 注册字符设备的方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*1.两种方式申请设备号*/
/* 1.1 静态申请设备号--仅仅得到设备号,注册设备*/
dev_t devno = MKDEV(260, 0);
register_chrdev_region(devno, 1, key_new_drv);
int register_chrdev_region(dev_t from, unsigned count, const char * name);
	// 参数一: 设备号
	// 参数二: 设备的个数
	// 参数三: 描述字符串 --- /proc/devices
	// 返回值: 0 成功, -1 失败
/* 1.2 动态分配设备号*/
int alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name);
	// 参数一: 保存主设备号的变量地址
	// 参数二: 次设备号的起始地址
	// 参数三: 设备的个数
	// 参数四: 描述字符串 --- /proc/devices
	// 返回值: 0 成功, -1 失败

/*2.动态分配一个cdev结构对象*/
cdev_alloc(void);
/*3.初始化cdev对象*/
cdev_init(struct cdev * cdev, const struct file_operations * fops);
/*4.将当前的cdev注册到系统中*/
cdev_add(struct cdev * p, dev_t dev, unsigned count);
	// 参数二:设备号
	// 参数三:设备的个数,一般都填1


/* 
	以上1、2、3、4步骤等同于用
	 int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
		 此函数内部会创建并初始化cdev对象, 当第一个参数为0时,自动分配设备号。
*/

cdev_del();
unregister_chrdev();
unregister_chrdev_region

3. 申请中断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 注册中断的处理方法
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,	    
            const char *name, void *dev)
    		//参数一: 中断号,  IRQ_EINT(x) x为外部中断号  arch/../mach/irqs.h中有枚举
    		//参数二: 中断处理函数  typedef irqreturn_t (*irq_handler_t)(int, void *);
    		//参数三: 中断的触发方式,include/linux/interrupt.h文件中定义
    					#define IRQF_TRIGGER_NONE	0x00000000
                        #define IRQF_TRIGGER_RISING	0x00000001
                        #define IRQF_TRIGGER_FALLING	0x00000002
                        #define IRQF_TRIGGER_HIGH	0x00000004
                        #define IRQF_TRIGGER_LOW	0x00000008
                        #define IRQF_TRIGGER_MASK	(IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
                                         IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
                        #define IRQF_TRIGGER_PROBE	0x00000010
    		//参数四: /proc/interrupts中显示的字符串
    		//参数五: 传递给中断处理函数的参数
            //返回值:
    
// irq_handler_t的原型
typedef irqreturn_t (*irq_handler_t)(int, void *);
//返回值如下
/**
 * enum irqreturn
 * @IRQ_NONE		interrupt was not from this device
 * @IRQ_HANDLED		interrupt was handled by this device
 * @IRQ_WAKE_THREAD	handler requests to wake the handler thread
 */
enum irqreturn {
	IRQ_NONE		= (0 << 0),
	IRQ_HANDLED		= (1 << 0),  //正常处理结束
	IRQ_WAKE_THREAD		= (1 << 1),
};

typedef enum irqreturn irqreturn_t;
    
//释放中断
void free_irq(unsigned int irq, void * dev_id)
    	//参数一: 中断号
    	//参数二: 和request_irq()函数的最后一个参数保持一致

4. 文件io模型实现之阻塞和非阻塞

实现阻塞

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/*实现阻塞的步骤*/
/* 1 将当前进程状态设置为TASK_INTERRUPTIBLE : 可以被信号唤醒 */
set_current_state(TASK_INTERRUPTIBLE);
/* 2 将当前进程加入到一个等待队列 */
add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait)
/* 3 让出调度权 */
schedule();

/* 以上三个函数等同于wait_event_interruptible */
wait_event_interruptible(struct wait_queue_head_t wq, int condition);
	//参数一: 等待队列头
	//参数二: 条件,如果为假,就休眠,否则继续执行

/* 资源可达的时候需要唤醒进程 */
wake_up_interruptible(x)
    
/* 定义一个等待队列头 */
struct wait_queue_head_t wq_head;
/* 初始化等待队列 */
init_waitqueue_head(wq_head);

实现非阻塞

1
2
3
4
5
6
7
8
9
//在应用程序中设定非阻塞模式
int fd = open("/dev/key0", O_RDWR|O_NOBLOCK);
 	// 有数据就得到数据,没有数据就得到一个出错码 -EAGAIN

//在驱动xxx_read中要区分是阻塞还是非阻塞
if ( (filp->f_flags & O_NONBLOCK) && 没有数据) //非阻塞且没有数据,就返回出错码
{
    return -EAGAIN;
}

5. 按键驱动

v1版本

实现按键触发中断

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>

#define IR_GPIO GPIOH(8)


#define USE_STATIC_MAJOR 1
#define KEY_MAJOR 260

struct s5pv210_key {
        dev_t   devno;
        int dev_major;
        int irqno;
        struct cdev *cdev;
        struct class *cls;
        struct device *dev;
};
struct s5pv210_key *key_dev;

static int key_drv_open(struct inode *inode, struct file *filp)
{
        return 0;
}


static int key_drv_close(struct inode *inode, struct file *filp)
{
        printk("-------^_^ %s-------\n", __FUNCTION__);
        return 0;
}

struct file_operations key_fops = {
        .open = key_drv_open,
        .release = key_drv_close,
};

irqreturn_t key_irq_svc(int irqno, void *dev_id)
{
        printk(" --------- ^-^ ---------%s \n", __FUNCTION__);
        return IRQ_HANDLED;
}


static int __init key_drv_init(void)
{
        int ret;
        key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
        if (key_dev == NULL) {
                printk(KERN_ERR"kzalloc error\n");
                return -ENOMEM;
        }
        #ifdef USE_STATIC_MAJOR
        key_dev->devno = MKDEV(KEY_MAJOR, 32);
        ret = register_chrdev_region(key_dev->devno, 1, "key");         /*申请设备号*/
        if (ret < 0) {
                printk(KERN_ERR"register chrdev failed\n");
                goto err_free;
        }
        #else
        ret = alloc_chrdev_region(&key_dev->devno, 32, 1, "key");
        if (ret != 0) {
                /* 出错 */
                printk(KERN_ERR"register chrdev failed\n");
                goto err_free;
        }
         #endif

        key_dev->cdev = cdev_alloc();                   /*动态分配一个struct cdev对象*/
        cdev_init(key_dev->cdev, &key_fops);            /*初始化cdev的fops*/
        cdev_add(key_dev->cdev, key_dev->devno, 1);     /*将cdev注册到系统中*/

        /* 自动创建设备节点 */
        key_dev->cls = class_create(THIS_MODULE, "key_cls");
        if (IS_ERR(key_dev->cls)) {
                printk(KERN_ERR"class create error\n");
                ret = PTR_ERR(key_dev->cls);
                goto err_unregister;
        }

        key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major, 0), NULL, "key%d", 0);
        if (IS_ERR(key_dev->dev)) {
                printk(KERN_ERR"device create error\n");
                ret = PTR_ERR(key_dev->dev);
                goto err_class_destory;
        }

        /* 初始化硬件 */
        // 中断号获取 IRQ_EINT(x); irqs.h
        //key_dev->irqno = IRQ_EINT(1);
        key_dev->irqno = gpio_to_irq(IR_GPIO);
        ret = request_irq(key_dev->irqno, key_irq_svc, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "KEY", NULL);
        if (ret != 0) {
                printk(KERN_ERR"request irq error\n");
                ret = -EBUSY;
                goto err_device_destory;
        }

err_device_destory:
        device_destroy(key_dev->cls, key_dev->devno);
err_class_destory:
        class_destroy(key_dev->cls);
err_unregister:
        cdev_del(key_dev->cdev);
        unregister_chrdev_region(key_dev->devno, 1);
err_free:
        kfree(key_dev);
        return ret;
}

static void __exit key_drv_exit(void)
{
        class_destroy(key_dev->cls);
        cdev_del(key_dev->cdev);
        unregister_chrdev_region(key_dev->devno, 1);
        kfree(key_dev);
}
module_init(key_drv_init);
module_exit(key_drv_exit);

MODULE_LICENSE("GPL");

基于linux操作系统下s5pv210板子的按键中断实验

NanoPi—M1(H3)———基于该平台的一个内核中的按键中断程序开发历程

v2版本

识别按键

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <linux/input.h> // KEY_DOWN
//设计一个按键数据包
struct key_event {
    int code; //按键的名字 --- 例如:下键、回车键
    int value; //按键的状态 --- 按下和抬起 1/0
};

struct s5pv210_key {
        dev_t   devno;
        int dev_major;
        int irqno;
        struct cdev *cdev;
        struct class *cls;
        struct device *dev;
	    struct key_event evnet;
};
struct s5pv210_key *key_dev;


irqreturn_t key_irq_svc(int irqno, void *dev_id)
{
        printk(" --------- ^-^ ---------%s \n", __FUNCTION__);
    	int value = gpio_get_value(S5PV210_GPH0(1));
    
    	if (value) {
            /* 抬起 */
            printk(" key up \n");
            key_dev->evnet.code = KEY_DOWN; //#include <linux/input.h>
            key_dev->event.value = 0;
        } else {
            printk(" key down \n");
            key_dev->evnet.code = KEY_DOWN;
            key_dev->event.value = 1;   
        }
        return IRQ_HANDLED;
}
ssize_t key_drv_read(struct file (filp, char __user *buf, size_t count, loff_t *fpos)
{
    int ret = copy_to_user(buf, &key_dev->event, count);
    if (ret > 0){
        printk("copy_to_user error\n");
        return -EFAULT;
    }
    memset(&key_dev->event, 0, sizeof(struct key_event));
    return count;
}

应用程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <linux/input.h> // KEY_DOWN grep -nHr "KEY_DOWN" /usr/include/
int fd = open("/dev/key0", O_RDWR);
if (fd < 0) {
    perror("open");
    exit(1);
}
while(1) {
    ret = read(fd, &event, sizeof(struct key_event));
    if (ret < 0) {
        perror("open");
        exit(1);        
    }
    if (event.code == KEY_DOWN) {
        if (event.value) {
            printf("KEY DOWN : UP");
        } else {
            printf("KEY DOWN : DOWN");
        }
    }

}

v3版本

v2版本的应用程序在运行时不停的读取按键状态,非常耗费CPU资源。考虑使用阻塞方式将读不到内容时将进程挂起。

在按键中断程序中使用阻塞方式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <linux/sched.h> //
#include <linux/wait.h>
struct s5pv210_key {
        dev_t   devno;
        int dev_major;
        int irqno;
        struct cdev *cdev;
        struct class *cls;
        struct device *dev;
	    struct key_event evnet;
    	struct wait_queue_head_t wq_head;
    	int have_data; // 一个标志,表示是否有数据,在open函数中将此标志置为0
};
struct s5pv210_key *key_dev;
/*在key_drv_init函数中初始化等待队列*/
static int __init key_drv_init(void) 
{
    init_waitqueue_head(key_dev->wq_head);
}

/*在read函数中如果读不到数据就将进程休眠*/
ssize_t key_drv_read(struct file (filp, char __user *buf, size_t count, loff_t *fpos)
{
    int ret = 0
    /*判断是否有资源*/
    wait_event_interruptible(key_dev->wq_head, key_dev->have_data);    
    /**/
    ret = copy_to_user(buf, &key_dev->event, count);
    if (ret > 0){
        printk("copy_to_user error\n");
        return -EFAULT;
    }
    /* 清空缓冲区 为下一次读取操作做准备 */
    memset(&key_dev->event, 0, sizeof(struct key_event));
    /* 同时也要清空标志 */
    key_dev->have_data = 0;
    return count;
}
                     
/* 在中断服务函数中唤醒挂起的进程 */                     
irqreturn_t key_irq_svc(int irqno, void *dev_id)
{
        printk(" --------- ^-^ ---------%s \n", __FUNCTION__);
    	int value = gpio_get_value(S5PV210_GPH0(1));
    
    	if (value) {
            /* 抬起 */
            printk(" key up \n");
            key_dev->evnet.code = KEY_DOWN; //#include <linux/input.h>
            key_dev->event.value = 0;
        } else {
            printk(" key down \n");
            key_dev->evnet.code = KEY_DOWN;
            key_dev->event.value = 1;   
        }
    	/* 此时有数据可以读写了 */
    	key_dev->have_data = 1;
    	/* 唤醒等待队列 */
    	wake_up_interruptible(&key_dev->wq_head);
        return IRQ_HANDLED;
}

再看看驱动中区分是否阻塞需要注意的地方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 在本例中 */
ssize_t key_drv_read(struct file (filp, char __user *buf, size_t count, loff_t *fpos)
{
    int ret = 0;
    /* 区分阻塞还是非阻塞 */
    if ((filp->f_flags & O_NONBLOCK) && !key_dev->have_data) {
        return -EAGAIN;
    }
    
    /*判断是否有资源*/
    wait_event_interruptible(key_dev->wq_head, key_dev->have_data);    
    /**/
    ret = copy_to_user(buf, &key_dev->event, count);
    if (ret > 0){
        printk("copy_to_user error\n");
        return -EFAULT;
    }
    /* 清空缓冲区 为下一次读取操作做准备 */
    memset(&key_dev->event, 0, sizeof(struct key_event));
    /* 同时也要清空标志 */
    key_dev->have_data = 0;
    return count;
}

手头只有NanoPi M1的板子,试验暂时还没做,加入TODO。