驅動開發 —— 中斷號與中斷編程


 目錄:

  1、中斷號

  2、獲取中斷號

  3、實現中斷處理

  4、中斷編程—實現字符設備驅動框架

  5、驅動實現將硬件數據傳遞給數據

  6、示例

 

1、中斷號

  中斷號是系統分配給每個中斷源的代號,以便識別和處理。在采用向量中斷方式的中斷系統中,CPU必須通過它才可以找到中斷服務程序的入口地址,實現程序的轉移。

  在ARM裸機中實現中斷需要配置:

1 I/O口為中斷模式,觸發方式,I/O口中斷使能
2 設置GIC中斷使能,分發配置,分發總使能,CPU外部中斷接口使能,中斷優先級

  在linux內核中實現中斷,只需要知道:

1 中斷號是什么,怎么得到中斷號
2 中斷處理方法

 

 2、獲取中斷號的方法:

  1)宏定義
    在沒有設備樹的內核中,中斷號定義為宏,IRQ_EINT
  
2)設備樹文件中     arch/arm/boot/dts/exynos4412-fs4412.dts

  1)看原理圖,芯片手冊找到中斷源對應的中斷號SPI Port  No

  

 

     

 

   2)進入設備樹,在arch/arm/boot/dts/exynos4x12-pinctrl.dtsi

 1  gpx1: gpx1 {
 2                     gpio-controller;
 3                     #gpio-cells = <2>;
 4 
 5                     interrupt-controller;  //中斷控制器
 6                     interrupt-parent = <&gic>;  //繼承於gic
 7                     interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
 8                                  <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
 9                     #interrupt-cells = <2>; //子繼承的interrupts的長度
10             };

  括號中的24、 25等對應於SPI Port No,以上是系統中已經定義好的節點

在編程中,需要定義自己的節點,用來描述按鍵,打開可編輯的設備樹文件:

arch/arm/boot/dts/exynos4412-fs4412.dts,進入文件。

  3)定義節點,描述當前設備用的中斷號

1 key_int_node{
2             compatible = "test_key";
3             interrupt-parent = <&gpx1>;  //繼承於gpx1
4             interrupts = <2 4>;      //2表示第幾個中斷號,4表示觸發方式為下降沿
5         };               //interrupts里長度由父母的-cell決定

  再舉個栗子,設置k4 --- GPX3_2(XEINT26) 的節點,中斷號

1 key_int_node{
2              compatible = "test_key";
3              interrupt-parent = <&gpx3>;  //繼承於gpx3
4              interrupts = <2 4>;      //2表示第2個中斷號,4表示觸發方式為下降沿
5          };  

    中斷號的定位方法:

    看I/O引腳,GPX1_2,中斷號就是GPX1里面的第2個

  4)編譯設備樹:make dtbs

    更新設備樹文件: cp  -raf arch/arm/boot/dts/exynos4412-fs4412.dtb   /tftpboot/

    查看定義的節點:在根目錄的 proc/device-tree/目錄下  

    

 

 3、實現中斷處理方法

  在驅動中通過代碼獲取到中斷號,並且申請中斷

  先看一下中斷相關的函數:

 1 a,獲取到中斷號碼:
 2     int get_irqno_from_node(void)
 3     {
 4         // 獲取到設備樹中的節點
 5         struct device_node *np = of_find_node_by_path("/key_int_node");
 6         if(np){
 7             printk("find node ok\n");
 8         }else{
 9             printk("find node failed\n");
10         }
11 
12         // 通過節點去獲取到中斷號碼
13         int irqno = irq_of_parse_and_map(np, 0);
14         printk("irqno = %d\n", irqno);
15         
16         return irqno;
17     }
18 b,申請中斷 19 int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev) 20 參數1: irq 設備對應的中斷號 21 參數2: handler 中斷的處理函數 22 typedef irqreturn_t (*irq_handler_t)(int, void *); 23 參數3:flags 觸發方式 24 #define IRQF_TRIGGER_NONE 0x00000000 //內部控制器觸發中斷的時候的標志 25 #define IRQF_TRIGGER_RISING 0x00000001 //上升沿 26 #define IRQF_TRIGGER_FALLING 0x00000002 //下降沿 27 #define IRQF_TRIGGER_HIGH 0x00000004 // 高點平 28 #define IRQF_TRIGGER_LOW 0x00000008 //低電平觸發 29 參數4:name 中斷的描述,自定義,主要是給用戶查看的 30 /proc/interrupts 31 參數5:dev 傳遞給參數2中函數指針的值 32 返回值: 正確為0,錯誤非0 33 34 35 參數2的賦值:即中斷處理函數 36 irqreturn_t key_irq_handler(int irqno, void *devid) 37 { 38 return IRQ_HANDLED; 39 } 43 44 c, 釋放中斷: 45 void free_irq(unsigned int irq, void *dev_id) 46 參數1: 設備對應的中斷號 47 參數2:與request_irq中第5個參數保持一致

代碼實現獲取中斷號,並注冊中斷,按下按鍵引發中斷,打印信息

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/fs.h>
 4 #include <linux/device.h>
 5 #include <asm/uaccess.h>
 6 #include <asm/io.h>
 7 #include <linux/slab.h>
 8 #include <linux/of.h>
 9 #include <linux/of_irq.h>
10 #include <linux/interrupt.h>
11 
12 int irqno;    //中斷號
13 
14 
15 irqreturn_t key_irq_handler(int irqno, void *devid)
16 {
17     printk("----------%s---------",__FUNCTION__);
18     return IRQ_HANDLED;
19 }
20 
21 
22 //獲取中斷號
23 int get_irqno_from_node(void)
24 {
25     //獲取設備樹中的節點
26     struct device_node *np = of_find_node_by_path("/key_int_node");
27     if(np){
28         printk("find node success\n");
29     }else{
30         printk("find node failed\n");
31     }
32 
33     //通過節點去獲取中斷號
34     int irqno = irq_of_parse_and_map(np, 0);
35     printk("iqrno = %d",irqno);
36 
37     return irqno;
38 }
39 
40 
41 
42 static int __init key_drv_init(void)
43 {
44     //演示如何獲取到中斷號
45     int ret;
46     
47     irqno = get_irqno_from_node();
48 
49     ret = request_irq(irqno, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
50         "key3_eint10", NULL);
51     if(ret != 0)
52     {
53         printk("request_irq error\n");
54         return ret;
55     }
56     
57     return 0;
58 }
59 
60 static void __exit key_drv_exit(void)
61 {
62     free_irq(irqno, NULL);  //free_irq與request_irq的最后一個參數一致
63 }
64 
65 
66 
67 module_init(key_drv_init);
68 module_exit(key_drv_exit);
69 
70 MODULE_LICENSE("GPL");
key_drv.c

測試效果:

 

按鍵按下,打印信息,但出現了按鍵抖動 

cat /proc/interrupt

 

  4、 中斷編程 --- 字符設備驅動框架

 1 // 1,設定一個全局的設備對象
 2 key_dev = kzalloc(sizeof(struct key_desc),  GFP_KERNEL);
 3 
 4 // 2,申請主設備號
 5 key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
 6 
 7 // 3,創建設備節點文件
 8 key_dev->cls = class_create(THIS_MODULE, "key_cls");
 9 key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major,0), NULL, "key0");
10 
11 // 4,硬件初始化:
12         a.地址映射
13         b.中斷申請

 

  5、驅動實現將硬件所產生的數據傳遞給用戶

  1)硬件如何獲取數據

key: 按下和抬起: 1/0
讀取key對應的gpio的狀態,可以判斷按下還是抬起
    
讀取key對應gpio的寄存器--數據寄存器 
//讀取數據寄存器
int value = readl(key_dev->reg_base + 4) & (1<<2);

 

   2)驅動傳遞數據給用戶

在中斷處理中填充數據:
     key_dev->event.code = KEY_ENTER;
     key_dev->event.value = 0;
在xxx_read中獎數據傳遞給用戶
     ret = copy_to_user(buf, &key_dev->event,  count);

  3)用戶獲取數據

    while(1)
    {
        read(fd, &event, sizeof(struct key_event));

        if(event.code == KEY_ENTER)
        {
            if(event.value)
            {
                printf("APP__ key enter pressed\n");
            }else{
                printf("APP__ key enter up\n");
            }
        }
    }

  6、示例:

  1 #include <linux/init.h>
  2 #include <linux/module.h>
  3 #include <linux/of.h>
  4 #include <linux/of_irq.h>
  5 #include <linux/interrupt.h>
  6 #include <linux/slab.h>
  7 #include <linux/fs.h>
  8 #include <linux/device.h>
  9 #include <linux/kdev_t.h>
 10 #include <linux/err.h>
 11 #include <linux/device.h>
 12 #include <asm/io.h>
 13 #include <asm/uaccess.h>
 14 
 15 
 16 #define GPXCON_REG 0X11000C20   //不可以從數據寄存器開始映射,要配置寄存器
 17 #define KEY_ENTER  28
 18 
 19 //0、設計一個描述按鍵的數據的對象
 20 struct key_event{
 21     int code;    //按鍵類型:home,esc,enter
 22     int value;   //表狀態,按下,松開
 23 };
 24 
 25 //1、設計一個全局對象——— 描述key的信息
 26 struct key_desc{
 27     unsigned int dev_major;
 28     int irqno;  //中斷號
 29     struct class  *cls;
 30     struct device *dev;
 31     void *reg_base;
 32     struct key_event event;
 33 };
 34 
 35 struct key_desc *key_dev;
 36 
 37 
 38 irqreturn_t key_irq_handler(int irqno, void *devid)
 39 {
 40     printk("----------%s---------",__FUNCTION__);
 41 
 42     int value;
 43     //讀取按鍵狀態
 44     value = readl(key_dev->reg_base + 4) & (0x01<<2);
 45     
 46     if(value){
 47         printk("key3 up\n");
 48         key_dev->event.code  = KEY_ENTER;
 49         key_dev->event.value = 0;
 50     }else{
 51         printk("key3 down\n");
 52         key_dev->event.code  = KEY_ENTER;
 53         key_dev->event.value = 1;
 54     }
 55     return IRQ_HANDLED;
 56 }
 57 
 58 
 59 //獲取中斷號
 60 int get_irqno_from_node(void)
 61 {
 62     int irqno;
 63     //獲取設備樹中的節點
 64     struct device_node *np = of_find_node_by_path("/key_int_node");
 65     if(np){
 66         printk("find node success\n");
 67     }else{
 68         printk("find node failed\n");
 69     }
 70 
 71     //通過節點去獲取中斷號
 72     irqno = irq_of_parse_and_map(np, 0);
 73     printk("iqrno = %d",key_dev->irqno);
 74 
 75     return irqno;
 76 }
 77 
 78 ssize_t key_drv_read (struct file * filp, char __user * buf, size_t count, loff_t * fops)
 79 {
 80     //printk("----------%s---------",__FUNCTION__);
 81     int ret;
 82     ret = copy_to_user(buf, &key_dev->event, count);
 83     if(ret > 0)
 84     {
 85         printk("copy_to_user error\n");
 86         return -EFAULT;
 87     }
 88 
 89     //傳遞給用戶數據后,將數據清除,否則APP每次讀都是第一次的數據
 90     memset(&key_dev->event, 0, sizeof(key_dev->event));
 91     return count;
 92 }
 93 
 94 ssize_t key_drv_write (struct file *filp, const char __user * buf, size_t count, loff_t * fops)
 95 {
 96     printk("----------%s---------",__FUNCTION__);
 97     return 0;
 98 }
 99 
100 int key_drv_open (struct inode * inode, struct file *filp)
101 {
102     printk("----------%s---------",__FUNCTION__);
103     return 0;
104 }
105 
106 int key_drv_close (struct inode *inode, struct file *filp)
107 {
108     printk("----------%s---------",__FUNCTION__);
109     return 0;
110 }
111 
112 
113 const struct file_operations key_fops = {
114     .open    = key_drv_open,
115     .read    = key_drv_read,
116     .write   = key_drv_write,
117     .release = key_drv_close,
118 
119 };
120 
121 
122 
123 static int __init key_drv_init(void)
124 {
125     //演示如何獲取到中斷號
126     int ret;
127     
128     //1、設定全局設備對象並分配空間
129     key_dev = kzalloc(sizeof(struct key_desc), GFP_KERNEL);  //GFP_KERNEL表正常分配內存
130                           //kzalloc相比於kmalloc,不僅分配連續空間,還會將內存初始化清零
131 
132     //2、動態申請設備號
133     key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);
134 
135     //3、創建設備節點文件
136     key_dev->cls = class_create(THIS_MODULE, "key_cls");
137     key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->dev_major, 0), NULL, "key0");
138 
139     //4、硬件初始化 -- 地址映射或中斷申請    
140     
141     key_dev->reg_base = ioremap(GPXCON_REG,8);
142 
143     key_dev->irqno = get_irqno_from_node();
144     
145     ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, 
146         "key3_eint10", NULL);
147     if(ret != 0)
148     {
149         printk("request_irq error\n");
150         return ret;
151     }
152 
153     //a. 硬件如何獲取數據
154     
155     
156     
157     return 0;
158 }
159 
160 static void __exit key_drv_exit(void)
161 {
162     iounmap(GPXCON_REG);
163     free_irq(key_dev->irqno, NULL);  //free_irq與request_irq的最后一個參數一致
164     device_destroy(key_dev->cls, MKDEV(key_dev->dev_major, 0));
165     class_destroy(key_dev->cls);
166     unregister_chrdev(key_dev->dev_major, "key_drv");
167     kfree(key_dev);
168 }
169 
170 
171 
172 module_init(key_drv_init);
173 module_exit(key_drv_exit);
174 
175 MODULE_LICENSE("GPL");
key_drv.c
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/stat.h>
 7 #include <fcntl.h>
 8 
 9 
10 #define KEY_ENTER  28
11 
12 //0、設計一個描述按鍵的數據的對象
13 struct key_event{
14     int code;    //按鍵類型:home,esc,enter
15     int value;   //表狀態,按下,松開
16 };
17 
18 
19 int main(int argc, char *argv[])
20 {
21     struct key_event event;
22     int fd;
23     fd = open("/dev/key0", O_RDWR);
24     if(fd < 0)
25     {
26         perror("open");
27         exit(1);
28     }
29 
30     while(1)
31     {
32         read(fd, &event, sizeof(struct key_event));
33 
34         if(event.code == KEY_ENTER)
35         {
36             if(event.value)
37             {
38                 printf("APP__ key enter down\n");
39             }else{
40 
41                 printf("APP__ key enter up\n");
42             }
43         }
44     }
45 
46     close(fd);
47 
48     return 0;
49 }
key_test.c
 1 ROOTFS_DIR = /home/linux/source/rootfs#根文件系統路徑
 2 
 3 APP_NAME = key_test
 4 MODULE_NAME = key_drv
 5 
 6 CROSS_COMPILE = /home/linux/toolchains/gcc-4.6.4/bin/arm-none-linux-gnueabi-
 7 CC = $(CROSS_COMPILE)gcc
 8 
 9 ifeq ($(KERNELRELEASE),)
10 
11 KERNEL_DIR = /home/linux/kernel/linux-3.14-fs4412          #編譯過的內核源碼的路徑
12 CUR_DIR = $(shell pwd)     #當前路徑
13 
14 all:
15     make -C $(KERNEL_DIR) M=$(CUR_DIR) modules  #把當前路徑編成modules
16     $(CC) $(APP_NAME).c -o $(APP_NAME)
17     @#make -C 進入到內核路徑
18     @#M 指定當前路徑(模塊位置)
19 
20 clean:
21     make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
22 
23 install:
24     sudo cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)/drv_module     #把當前的所有.ko文件考到根文件系統的drv_module目錄
25 
26 else
27 
28 obj-m += $(MODULE_NAME).o    #指定內核要把哪個文件編譯成ko
29 
30 endif
Makefile

 執行用戶程序,按下按鍵可以看到信息

 

 

    退出用戶程序,按下按鍵,也會打印相應信息。

 

 

   查看設備與中斷節點信息:

 

   再看下CPU情況:

 

   可以看到key_test應用程序占了很高的CPU,什么原因呢?

在應用程序中,是通過while循環,一直read內核的信息,當有按鍵中斷發生的時候,就會對key_event賦值,在while循環里判斷,進而打印出來,這樣在用戶空間與內核空間一直來回切換,一直read會十分消耗CPU資源。

 解決思路:當有中斷發生時,才來調用read,沒有數據產生,跳出進程調度,進程休眠。

接下來學習IO模型,來解決這個問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM