在读者学习本章以及后续章节之前,最好拥有中断裸机基础,可以参考:中断编程

 

通过裸机系列的学习,我们可以知道异常的触发分为下面几个过程:

1. 在规定地址设置异常向量表

2. 保存各种寄存器的值(保存现场)

2. 执行异常处理函数(处理现场)

3. 恢复执行(恢复现场)

 

如u-boot中arch/arm/cpu/armv7/start.S中代码:

 1 .globl _start
 2 _start: b    reset
 3     ldr    pc, _undefined_instruction
 4     ldr    pc, _software_interrupt
 5     ldr    pc, _prefetch_abort
 6     ldr    pc, _data_abort
 7     ldr    pc, _not_used
 8     ldr    pc, _irq
 9     ldr    pc, _fiq
10 
11 _undefined_instruction: .word undefined_instruction
12 _software_interrupt:    .word software_interrupt
13 _prefetch_abort:    .word prefetch_abort
14 _data_abort:        .word data_abort
15 _not_used:        .word not_used
16 _irq:            .word irq
17 _fiq:            .word fiq
18 
19 ...
20 
21 irq:
22     get_irq_stack        /* 设置栈 */
23     irq_save_user_regs    /* 保存寄存器的值 */
24     bl    do_irq            /* 处理中断 */
25     irq_restore_user_regs    /* 恢复· */
26 
27     .align    5
28 
29 ...

 

Linux的异常处理其实也和裸机中的流程一样,只不过Linux要对所有的异常都进行具体分析处理

 

Linux内核所做的中断初始化如下,其中b start_kernel代码在arch/arm/kernel/head-common.S中 

b start_kernel
    ...
    local_irq_disable();   /* 关中断 */
    ...
    setup_arch(&command_line);
        paging_init(mdesc);
            devicemaps_init(mdesc);
                early_trap_init(vectors);    /* 设置异常向量表 */

    ...
    trap_init();           /* 空函数 */
    ...
    early_irq_init();      /* 初始化irq_desc数组 */
    init_IRQ();            /* 芯片相关的中断的初始化 */
    ...
    local_irq_enable();    /* 开中断 */

1. 首先关闭中断,因为异常向量表还没有设置,如果此时触发中断,程序会跑飞

2. 之后设置异常向量表

a. 申请一块内存,用异常向量表填充这块内存区域

b. 通过异常向量表的虚拟地址找到对应的物理地址,并把这块内存区域再次映射到0xFFFF 0000区域

c. 之后检查内存映射,如果不是高端映射,则映射到0地址(页表会覆盖掉前面的高端映射)

3. 初始化irq_desc数组,这个数组用于存储中断数据,如中断号、中断类型等

4. 芯片相关的中断初始化、开中断

 

当发生中断时,会跳转到vector_irq + offset的地址执行代码,与裸机相同,代码会进行保存现场、处理现场、恢复现场的操作

处理现场会调用asm_do_IRQ()函数,调用层次如下:

asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
 -> handle_IRQ(irq, regs);
   -> generic_handle_irq(irq);
     -> struct irq_desc *desc = irq_to_desc(irq);  /* 将中断号转化为irq_desc数组项 */
     -> generic_handle_irq_desc(irq, desc);
       -> desc->handle_irq(irq, desc);     /* 最终调用执行初始化阶段注册的通用函数 */
         -> handle_level_irq(unsigned int irq, struct irq_desc *desc);
           -> handle_irq_event(desc);
             -> handle_irq_event_percpu(desc, action);
               -> action->handler(irq, action->dev_id)    /* 我们需要实现的驱动函数 */
                 -> action = action->next;    /* 共享中断要执行相同中断号的cation链表的所有中断 */

asm_do_IRQ()函数除此之外,还会清中断,因此我们在中断处理函数中不需要自己清中断

 

层次结构中的irq_desc有以下几个我们需要关注的成员:

struct irq_desc {
    struct irq_data        irq_data;    /* 每个irq和芯片数据传递给芯片功能 */
    ...
    irq_flow_handler_t    handle_irq;    /* 通用中断处理函数 */
    ...
    struct irqaction    *action;    /* IRQ action链表 */
    ...
}

代码中的struct irqaction中含有真正的中断处理函数:

struct irqaction {
    irq_handler_t        handler;     /* 中断处理函数 */
    unsigned long        flags;       /* 标志 */
    void            *dev_id;          /* 中断函数传入数据 */
    ...
    int            irq;               /* 中断号 */
    ...
}

代码中的中断函数格式和中断标志位如下:

typedef irqreturn_t (*irq_handler_t)(int, void *);

...

#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

 

因此我们需要做的就是向上注册中断数据和处理函数

在内核中,注册中断函数原型为:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

 

函数所做的事情有:

1. 分配、设置、注册irqaction

2. 设置中断引脚

3. 使能中断

 

释放申请的中断函数原型为:

void free_irq(unsigned int, void *);

 

 

二、等待队列

在中断编程中,中断通常会和等待队列一起使用。等待队列的作用是防止驱动中断读或写过程浪费CPU利用率。

进程通过执行下面步骤将自己加入到一个等待队列中

1. 定义等待队列头部,如wait_queue_head_t my_queue;

2. 调用init_wait_queue_head()初始化等待队列头部

3. 调用DECLARE_WAITQUEUE()创建一个等待队列的项

4. 调用add_wait_queue()把自己加入到等待队列中,该队列会在进程等待的条件满足时唤醒它。在其他地方写相关代码,在事件发生时,对等的队列执行wake_up()操作

5. 调用set_current_state()将进程状态变更为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE

6. 如果状态被置为TASK_INTERRUPTIBLE,则使用中断唤醒进程

7. 检查condition是否为真,为真则不休眠,如果为假,则调用scheduled()休眠

8. 当进程被唤醒的时候,它会再次检查条件是否为真。真就退出循环,否则再次调用scheduled()并一直重复这步操作

9. condition满足后,进程设置为TASK_RUNNING并通过remove_wait_queue()退出

 

需要注意的是,如果驱动程序中的read()和write()函数都实现了休眠功能,那么我们需要在read()被唤醒时调用wake_up(wait_queue_head_t *)系列函数唤醒write()函数

这样是为了防止写进程和读进程相互阻塞

wake_up()系列函数声明有:

/* 唤醒队列 */
wake_up(queue, condition); // condition为0时解除休眠
wake_up_interruptible(queue, condition);

/* 等待事件 */
wait_event(queue, condition); // condition为volatile变量,为1时休眠
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition);
wait_event_interruptible_timeout(queue, condition);

代码中wait_event()和wait_event_interrupt()的区别是wait_event_interrupt()设置了TASK_INTERRUPTIBLE标记,使得进程处于可中断(TASK_INTERRUPTIBLE)状态,从而睡眠进程可以通过接收信号被唤醒

本节代码中暂时使用不到wait_event()和wait_event_interrupt()函数

 

wait_event()和wait_event_interrupt()的差别:

wait_event()不能被Ctrl + C和kill -9命令打断,而wait_event_interrupt()都可以被打断

 

示例代码如下:

 1 static ssize_t gm_read(struct file *filp, char __user *buf, size_t len, loff_t * loff)
 2 {
 3     struct gm_dev *dev = filp->private_data;
 4     int count = len;        // 考虑边界条件
 5     int ret;
 6 
 7     DECLARE_WAITQUEUE(wait, current);
 8     mutex_lock(&dev->lock);
 9     add_wait_queue(&dev->r_head, &wait);
10     
11     while (dev->current_len == 0) {
12         if (filp->f_flags & O_NONBLOCK) {
13             ret = -EAGAIN;
14             goto out;
15         }
16 
17         __set_current_state(TASK_INTERRUPTIBLE);
18         mutex_unlock(&dev->lock);
19         schedule();
20         
21         if (signal_pending(current)) {
22             ret = -ERESTARTSYS;
23             goto out2;
24         }
25         mutex_lock(&dev->lock);
26     }
27     
28     if (count > dev->current_len)
29         count = dev->current_len;
30 
31     if (copy_to_user(buf, dev->mem, count)) {
32         ret = -EFAULT;
33         goto out;
34     }
35     else {
36         // 把后面的数据放到前面
37         memcpy(dev->mem, dev->mem + count, dev->current_len - count);
38         dev->current_len -= count;
39         ret = count;
40         printk("## GM ## READ %d\n", count);
41 
42         // 唤醒可能阻塞的写进程,注意是写进程
43         wake_up_interruptible(&dev->w_head);
44     }
45 
46 out:
47     mutex_unlock(&dev->lock);
48     
49 out2:
50     remove_wait_queue(&dev->r_head, &wait);
51     set_current_state(TASK_RUNNING);
52     
53     return ret;
54 }

 

 

有了上面的基础,现在我们可以实现按键中断字符驱动程序

三、按键中断字符驱动程序

key源代码:

3、中断分析以及按键中断
  1 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/device.h>
  7 #include <linux/irq.h>
  8 #include <linux/interrupt.h>
  9 #include <linux/wait.h>
 10 #include <linux/timer.h>
 11 #include <linux/gpio.h>
 12 #include <linux/sched.h>
 13 
 14 #include <asm/uaccess.h>
 15 #include <asm/irq.h>
 16 #include <asm/io.h>
 17 
 18 #include <mach/gpio.h>
 19 
 20 #define KEY_MAJOR        255
 21 
 22 struct pin_desc {
 23     int gpio;
 24     int val;
 25     char *name;
 26 };
 27 
 28 struct key_device {
 29     struct cdev cdev;
 30     wait_queue_head_t r_head;
 31     wait_queue_head_t w_head;
 32 };
 33 
 34 static struct pin_desc desc[4] = {
 35     { EXYNOS4_GPX3(2), 0x01, "KEY0" },
 36     { EXYNOS4_GPX3(3), 0x02, "KEY1" },
 37     { EXYNOS4_GPX3(4), 0x03, "KEY2" },
 38     { EXYNOS4_GPX3(5), 0x04, "KEY3" },
 39 };
 40 
 41 static int g_major = KEY_MAJOR;
 42 module_param(g_major, int, S_IRUGO);
 43 
 44 static struct key_device*    dev;
 45 static struct class*        scls;
 46 static struct device*        sdev;
 47 static unsigned char        key_val;
 48 
 49 static irqreturn_t key_interrupt(int irq, void *dev_id)
 50 {
 51     struct pin_desc *pindesc = (struct pin_desc *)dev_id;
 52     unsigned int tmp;
 53 
 54     tmp = gpio_get_value(pindesc->gpio);
 55 
 56     /* active low */
 57     printk(KERN_DEBUG "KEY %d: %08x\n", pindesc->val, tmp);
 58 
 59     if (tmp)
 60         key_val = pindesc->val;
 61     else
 62         key_val = pindesc->val | 0x80;
 63 
 64     set_current_state(TASK_RUNNING);
 65 
 66     return IRQ_HANDLED;
 67 }
 68 
 69 static ssize_t key_read(struct file *filp, char __user *buf, size_t len, loff_t * loff)
 70 {
 71     struct key_device *dev = filp->private_data;
 72 
 73     // 声明等待队列
 74     DECLARE_WAITQUEUE(rwait, current);
 75     add_wait_queue(&dev->r_head, &rwait);
 76 
 77     // 休眠
 78     __set_current_state(TASK_INTERRUPTIBLE);
 79     schedule();
 80 
 81     // 有数据
 82     copy_to_user(buf, &key_val, 1);
 83 
 84     remove_wait_queue(&dev->r_head, &rwait);
 85     set_current_state(TASK_RUNNING);
 86 
 87     return 1;
 88 }
 89 
 90 static int key_open(struct inode *nodep, struct file *filp)
 91 {
 92     struct key_device *dev = container_of(nodep->i_cdev, struct key_device, cdev);
 93     // 放入私有数据中
 94     filp->private_data = dev;
 95 
 96     int irq;
 97     int i, err = 0;
 98 
 99     for (i = 0; i < ARRAY_SIZE(desc); i++) {
100         if (!desc[i].gpio)
101             continue;
102 
103         irq = gpio_to_irq(desc[i].gpio);
104         err = request_irq(irq, key_interrupt, IRQ_TYPE_EDGE_BOTH, 
105                 desc[i].name, (void *)&desc[i]);
106         if (err)
107             break;
108     }
109     
110     if (err) {
111         i--;
112         for (; i >= 0; i--) {
113             if (!desc[i].gpio)
114                 continue;
115 
116             irq = gpio_to_irq(desc[i].gpio);
117             free_irq(irq, (void *)&desc[i]);
118         }
119         return -EBUSY;
120     }
121     
122     init_waitqueue_head(&dev->r_head);
123 
124     return 0;
125 }
126 
127 static int key_release(struct inode *nodep, struct file *filp)
128 {
129     // 释放中断
130     int irq, i;
131 
132     for (i = 0; i < ARRAY_SIZE(desc); i++) {
133         if (!desc[i].gpio)
134             continue;
135 
136         irq = gpio_to_irq(desc[i].gpio);
137         free_irq(irq, (void *)&desc[i]);
138     }
139 
140     return 0;
141 }
142 
143 static struct file_operations key_fops = {
144     .owner    = THIS_MODULE,
145     .read    = key_read,
146     .open        = key_open,
147     .release    = key_release,
148 };
149 
150 static int keys_init(void)
151 {
152     int ret;
153     int devt;
154     if (g_major) {
155         devt = MKDEV(g_major, 0);
156         ret = register_chrdev_region(devt, 1, "key");
157     }
158     else {
159         ret = alloc_chrdev_region(&devt, 0, 1, "key");
160         g_major = MAJOR(devt);
161     }
162 
163     if (ret)
164         return ret;
165 
166     dev = kzalloc(sizeof(struct key_device), GFP_KERNEL);
167     if (!dev) {
168         ret = -ENOMEM;
169         goto fail_alloc;
170     }
171 
172     cdev_init(&dev->cdev, &key_fops);
173     ret = cdev_add(&dev->cdev, devt, 1);
174     if (ret)
175         return ret;
176 
177     scls = class_create(THIS_MODULE, "key");
178     sdev = device_create(scls, NULL, devt, NULL, "key");
179 
180     return 0;
181 
182 fail_alloc:
183     unregister_chrdev_region(devt, 1);
184 
185     return ret;
186 }
187 
188 static void keys_exit(void)
189 {
190     dev_t devt = MKDEV(g_major, 0);
191 
192     device_destroy(scls, devt);
193     class_destroy(scls);
194 
195     cdev_del(&(dev->cdev));
196     kfree(dev);
197 
198     unregister_chrdev_region(devt, 1);
199 }
200 
201 module_init(keys_init);
202 module_exit(keys_exit);
203 
204 MODULE_LICENSE("GPL");
View Code

相关文章: