Chinaunix首页 | 论坛 | 博客
  • 博客访问: 55607
  • 博文数量: 16
  • 博客积分: 85
  • 博客等级: 民兵
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-07 23:54
文章分类
文章存档

2012年(11)

2011年(5)

我的朋友

分类:

2012-01-09 20:44:02

ARM架构Linux内核的异常处理体系结构,可以由arch/arm/kernel/entry-armv.S中的异常向量反应出来,
  1. .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

  2. .globl __vectors_start
  3. __vectors_start:
  4. ARM( swi SYS_ERROR0 )
  5. THUMB( svc #0 )
  6. THUMB( nop )
  7. W(b) vector_und + stubs_offset
  8. W(ldr) pc, .LCvswi + stubs_offset
  9. W(b) vector_pabt + stubs_offset
  10. W(b) vector_dabt + stubs_offset
  11. W(b) vector_addrexcptn + stubs_offset
  12. W(b) vector_irq + stubs_offset
  13. W(b) vector_fiq + stubs_offset

  14. .globl __vectors_end
  15. __vectors_end:
对于我来说理解这段代码有难度:
(1)找不到标号vector_xxxx
在网上找了.macro vector_stub, name, mode, correction=0 的相关解释。在arch/arm/kernel/entry-armv.S有宏定义

  1. .macro vector_stub, name, mode, correction=0
  2. .align 5

  3. vector_\name:
  4. .if \correction
  5. sub lr, lr, #\correction
  6. .endif

  7. @
  8. @ Save r0, lr_ (parent PC) and spsr_
  9. @ (parent CPSR)
  10. @
  11. stmia sp, {r0, lr} @ save r0, lr
  12. mrs lr, spsr
  13. str lr, [sp, #8] @ save spsr

  14. @
  15. @ Prepare for SVC32 mode. IRQs remain disabled.
  16. @
  17. mrs r0, cpsr
  18. eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
  19. msr spsr_cxsf, r0

  20. @
  21. @ the branch table must immediately follow this code
  22. @
  23. and lr, lr, #0x0f
  24. THUMB( adr r0, 1f )
  25. THUMB( ldr lr, [r0, lr, lsl #2] )
  26. mov r0, sp
  27. ARM( ldr lr, [pc, lr, lsl #2] )
  28. movs pc, lr @ branch to handler in SVC mode
  29. ENDPROC(vector_\name)

  30. .align 2
  31. @ handler addresses follow this label
  32. 1:
  33. .endm
原来vector_xxxx就在arch/arm/kernel/entry-armv.S中。

(2)stubs_offset的理解
vector为异常向量基址,unsigned long vectors = CONFIG_VECTORS_BASE。
在arch/arm/kernel/entry-armv.S中,stubs_offset定义为:
.equ    stubs_offset, __vectors_start + 0x200 - __stubs_start????????

在中找到如下解释:
“当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±32M)写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬 移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写 成搬移后的。

我们把搬移前的中断向量表中的irq入口地址记
irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start,
vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。

搬移后 vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断 向量中的中断入口地址的偏移量就是,200+vector_irq。在stubs中的偏移量再减去中断入口在向量表中的偏移量,即
200+ vector_irq-stubs_start-irq_PC+vectors_start
= (vector_irq-irq_PC) + vectors_start+200-stubs_start,
对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇 编器完成的,而后面的 vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的。”


汇编伪指令中equ的使用格式是:name   EQU   expr{,type}

memcpy的函数原型为:void *memcpy(void *dest, const void *src, size_t n);从下面代码的3个
memcpy()函数可以看出,vectors~vectors + 0x200存储的异常向量,B vector_,中断向量;vectors + 0x200~vectors + 0x1000存储的具体异常的处理分支,而这个分支视乎只是地址,vector_,跳转表。
arch/arm/kernel/entry-armv.S中的标号,那程序是怎么实现跳转的呢?????
参考http://blog.csdn.net/xavierxiao/article/details/6088050

  1. void __init early_trap_init(void)
  2. {
  3.     unsigned long vectors = CONFIG_VECTORS_BASE;
  4.     extern char __stubs_start[], __stubs_end[];
  5.     extern char __vectors_start[], __vectors_end[];
  6.     extern char __kuser_helper_start[], __kuser_helper_end[];
  7.     int kuser_sz = __kuser_helper_end - __kuser_helper_start;

  8.     /*
  9.      * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
  10.      * into the vector page, mapped at 0xffff0000, and ensure these
  11.      * are visible to the instruction stream.
  12.      */
  13.     memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
  14.     memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
  15.     memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

  16.     /*
  17.      * Do processor specific fixups for the kuser helpers
  18.      */
  19.     kuser_get_tls_init(vectors);

  20.     /*
  21.      * Copy signal return handlers into the vector page, and
  22.      * set sigreturn to be a pointer to these.
  23.      */
  24.     memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
  25.      sizeof(sigreturn_codes));
  26.     memcpy((void *)KERN_RESTART_CODE, syscall_restart_code,
  27.      sizeof(syscall_restart_code));

  28.     flush_icache_range(vectors, vectors + PAGE_SIZE);
  29.     modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
  30. }


疑问先放一下,以后再理解。刚才讲述了异常总的入口,及其相关的操作,接下来讲述中断,,,
中断的处理流程如下:
1)发生中断时,CPU执行异常向量vector_irq的代码。
2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ。
3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。
4)handle_irq会使用chip成员中的函数来设置硬件,比如清楚中断,禁止中断,重新使能中断等。
5)handle_irq逐个调用用户在action链表中注册的处理函数。

1)irq_desc结构
在include/linux/irq.h中。
注释很清楚,并且可以知道desc就是descriptor的缩写,方便记忆。
  1. struct timer_rand_state;
  2. struct irq_2_iommu;
  3. /**
  4.  * struct irq_desc - interrupt descriptor
  5.  * @irq:        interrupt number for this descriptor
  6.  * @timer_rand_state:    pointer to timer rand state struct
  7.  * @kstat_irqs:        irq stats per cpu
  8.  * @irq_2_iommu:    iommu with this irq
  9.  * @handle_irq:        highlevel irq-events handler [if NULL, __do_IRQ()]
  10.  * @chip:        low level interrupt hardware access
  11.  * @msi_desc:        MSI descriptor
  12.  * @handler_data:    per-IRQ data for the irq_chip methods
  13.  * @chip_data:        platform-specific per-chip private data for the chip
  14.  *            methods, to allow shared chip implementations
  15.  * @action:        the irq action chain
  16.  * @status:        status information
  17.  * @depth:        disable-depth, for nested irq_disable() calls
  18.  * @wake_depth:        enable depth, for multiple set_irq_wake() callers
  19.  * @irq_count:        stats field to detect stalled irqs
  20.  * @last_unhandled:    aging timer for unhandled count
  21.  * @irqs_unhandled:    stats field for spurious unhandled interrupts
  22.  * @lock:        locking for SMP
  23.  * @affinity:        IRQ affinity on SMP
  24.  * @node:        node index useful for balancing
  25.  * @pending_mask:    pending rebalanced interrupts
  26.  * @threads_active:    number of irqaction threads currently running
  27.  * @wait_for_threads:    wait queue for sync_irq to wait for threaded handlers
  28.  * @dir:        /proc/irq/ procfs entry
  29.  * @name:        flow handler name for /proc/interrupts output
  30.  */
  31. struct irq_desc {
  32.     unsigned int        irq;
  33.     struct timer_rand_state *timer_rand_state;
  34.     unsigned int *kstat_irqs;
  35. #ifdef CONFIG_INTR_REMAP
  36.     struct irq_2_iommu *irq_2_iommu;
  37. #endif
  38.     irq_flow_handler_t    handle_irq;
  39.     struct irq_chip        *chip;
  40.     struct msi_desc        *msi_desc;
  41.     void            *handler_data;
  42.     void            *chip_data;
  43.     struct irqaction    *action;    /* IRQ action list */
  44.     unsigned int        status;        /* IRQ status */

  45.     unsigned int        depth;        /* nested irq disables */
  46.     unsigned int        wake_depth;    /* nested wake enables */
  47.     unsigned int        irq_count;    /* For detecting broken IRQs */
  48.     unsigned long        last_unhandled;    /* Aging timer for unhandled count */
  49.     unsigned int        irqs_unhandled;
  50.     raw_spinlock_t        lock;
  51. #ifdef CONFIG_SMP
  52.     cpumask_var_t        affinity;
  53.     const struct cpumask    *affinity_hint;
  54.     unsigned int        node;
  55. #ifdef CONFIG_GENERIC_PENDING_IRQ
  56.     cpumask_var_t        pending_mask;
  57. #endif
  58. #endif
  59.     atomic_t        threads_active;
  60.     wait_queue_head_t wait_for_threads;
  61. #ifdef CONFIG_PROC_FS
  62.     struct proc_dir_entry    *dir;
  63. #endif
  64.     const char        *name;
  65. } ____cacheline_internodealigned_in_smp;


eg.
buttons.c
  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/fs.h>
  4. #include <linux/init.h>
  5. #include <linux/delay.h>
  6. #include <asm/irq.h>
  7. #include <linux/interrupt.h>
  8. #include <asm/uaccess.h>
  9. #include <asm/arch/regs-gpio.h>
  10. #include <asm/hardware.h>

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

  13. struct button_irq_desc {
  14.     int irq;
  15.     unsigned long flags;
  16.     char *name;
  17. };

  18. /* 用来指定按键所用的外部中断引脚及中断触发方式, 名字 */
  19. static struct button_irq_desc button_irqs [] = {
  20.     {IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */
  21.     {IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */
  22.     {IRQ_EINT2, IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */
  23.     {IRQ_EINT0, IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */
  24. };

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

  27. /* 等待队列:
  28.  * 当没有按键被按下时,如果有进程调用s3c24xx_buttons_read函数,
  29.  * 它将休眠
  30.  */
  31. static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

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


  34. static irqreturn_t buttons_interrupt(int irq, void *dev_id)
  35. {
  36.     volatile int *press_cnt = (volatile int *)dev_id;
  37.     
  38.     *press_cnt = *press_cnt + 1; /* 按键计数加1 */
  39.     ev_press = 1; /* 表示中断发生了 */
  40.     wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
  41.     
  42.     return IRQ_RETVAL(IRQ_HANDLED);
  43. }


  44. /* 应用程序对设备文件/dev/buttons执行open(...)时,
  45.  * 就会调用s3c24xx_buttons_open函数
  46.  */
  47. static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
  48. {
  49.     int i;
  50.     int err;
  51.     
  52.     for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
  53.         // 注册中断处理函数
  54.         err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags,
  55.                           button_irqs[i].name, (void *)&press_cnt[i]);
  56.         if (err)
  57.             break;
  58.     }

  59.     if (err) {
  60.         // 释放已经注册的中断
  61.         i--;
  62.         for (; i >= 0; i--)
  63.             free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
  64.         return -EBUSY;
  65.     }
  66.     
  67.     return 0;
  68. }


  69. /* 应用程序对设备文件/dev/buttons执行close(...)时,
  70.  * 就会调用s3c24xx_buttons_close函数
  71.  */
  72. static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
  73. {
  74.     int i;
  75.     
  76.     for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
  77.         // 释放已经注册的中断
  78.         free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
  79.     }

  80.     return 0;
  81. }


  82. /* 应用程序对设备文件/dev/buttons执行read(...)时,
  83.  * 就会调用s3c24xx_buttons_read函数
  84.  */
  85. static int s3c24xx_buttons_read(struct file *filp, char __user *buff,
  86.                                          size_t count, loff_t *offp)
  87. {
  88.     unsigned long err;
  89.     
  90.     /* 如果ev_press等于0,休眠 */
  91.     wait_event_interruptible(button_waitq, ev_press);

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

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

  97.     return err ? -EFAULT : 0;
  98. }

  99. /* 这个结构是字符设备驱动程序的核心
  100.  * 当应用程序操作设备文件时所调用的open、read、write等函数,
  101.  * 最终会调用这个结构中的对应函数
  102.  */
  103. static struct file_operations s3c24xx_buttons_fops = {
  104.     .owner = THIS_MODULE, /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
  105.     .open = s3c24xx_buttons_open,
  106.     .release = s3c24xx_buttons_close,
  107.     .read = s3c24xx_buttons_read,
  108. };

  109. /*
  110.  * 执行“insmod s3c24xx_buttons.ko”命令时就会调用这个函数
  111.  */
  112. static int __init s3c24xx_buttons_init(void)
  113. {
  114.     int ret;

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

  130. /*
  131.  * 执行”rmmod s3c24xx_buttons.ko”命令时就会调用这个函数
  132.  */
  133. static void __exit s3c24xx_buttons_exit(void)
  134. {
  135.     /* 卸载驱动程序 */
  136.     unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
  137. }

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

  141. /* 描述驱动程序的一些信息,不是必须的 */
  142. MODULE_AUTHOR(""); // 驱动程序的作者
  143. MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver"); // 一些描述信息
  144. MODULE_LICENSE("GPL"); // 遵循的协议

buttons_test.c
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/ioctl.h>

  5. int main(int argc, char **argv)
  6. {
  7.     int i;
  8.     int ret;
  9.     int fd;
  10.     int press_cnt[4];
  11.     
  12.     fd = open("/dev/buttons", 0); // 打开设备
  13.     if (fd < 0) {
  14.         printf("Can't open /dev/buttons\n");
  15.         return -1;
  16.     }

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

  25.         for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {
  26.             // 如果被按下的次数不为0,打印出来
  27.             if (press_cnt[i])
  28.                 printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]);
  29.         }
  30.     }
  31.     
  32.     close(fd);
  33.     return 0;
  34. }
对比前面没有中断的LEDS驱动程序,可以更好的掌握~


参考博客:
http://www.cnblogs.com/hoys/archive/2011/04/13/2015318.html


阅读(1498) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~