2440中断

目录


title: 中断
tags: ARM
date: 2018-10-22 21:04:32
---

2440中断

mark

ARM的寄存器

通用寄存器
备份寄存器(banked register)
CPSR:当前程序状态寄存器(Current Program Status Register);CPSR
SPSR:CPSR的备份寄存器:SPSR(Save Program Status Register)
    表示发生异常时这个寄存器会用来保存被中断的模式下他的CPSR.也就是保存上一个模式下的状态寄存器.
LR:被中断函数的下一个指令
--------------------------------------------------------------------------------------
usr  正常模式
sys
undefined(und)  未定义模式
Supervisor(svc) 管理模式
Abort(abt) 终止模式  -a 指令预取终止(读写某条错误的指令导致终止运行)
                     -b 数据访问终止 (读写某个地址,这个过程出错)
IRQ(irq) 中断模式
FIQ(fiq) 快中断模式

mark

mark

中断向量表如下:

mark

可以看下uboot的cpu\arm920t\start.S

.globl _start
_start: 
    b       reset
    ldr pc, _undefined_instruction
    ldr pc, _software_interrupt
    ldr pc, _prefetch_abort
    ldr pc, _data_abort
    ldr pc, _not_used
    ldr pc, _irq
    ldr pc, _fiq

_undefined_instruction: .word undefined_instruction
_software_interrupt:    .word software_interrupt
_prefetch_abort:    .word prefetch_abort
_data_abort:        .word data_abort
_not_used:      .word not_used
_irq:           .word irq
_fiq:           .word fiq

    .balignl 16,0xdeadbeef

异常优先级

  • 高优先级
  1. 复位
  2. 数据终止
  3. 快中断
  4. 中断IRQ
  5. 欲取终止
  • 低优先级
  1. 未定义指令,软件中断,

中断互斥:

  • 未定义指令和软件中断异常互斥,因为是软件中断指令,就不可能是未定义指令

异常之复位

  1. 复制当前 PC 和 CPSR 的值覆盖到 R14_svc 和 SPSR_svc 中。未定义保存的 PC 和 SPSR 的值
  2. 强制将 M[4:0]设置为 10011(管理模式),置位 CPSR 中的 I(禁止中断) 和 F(禁止fiq) 位并清除 CPSR 的 T(ARM状态) 位。
  3. 强制 PC 从 0x00 地址开始对下一条指令的取指。
  4. 在 ARM 状态恢复执行

小结: pc从0开始,禁止快中断,禁止中断,ARM指令状态

异常之未定义指令(最简单的中断处理程序)

中断处理

.text
.global _start

_start:
    b reset     //reset
    ldr pc,undef_addr   //未定义指令
    
undef_addr:
    .word do_und
do_und:
    /*
        保存现场
        {
            硬件操作
                1.保存cpsr到scpsr
                2.保存返回地址到lr中
            软件操作
                1.r0~r12寄存器
                2.返回地址lr
        }
        do_something
        恢复现场
        {
            恢复r0~r12
            恢复cpsr状态寄存器
        }
    */  
    //设置sp,这里正常的流程应该是在reset中设置sp的
    ldr sp,=0x34000000

    //保存现场,这个入栈的代码可以看反汇编调用函数的例子
    stmdb sp!, {r0-r12, lr}

    //do_something
    mrs r0, cpsr            //把cpsr的值读入r0,11011 后五位
    ldr r1, =und_string     //汇编调用字符串
    bl interupt_und

    //恢复现场,同时恢复cpsr,这个异常pc=lr=pc+4
    ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */

主函数手动触发

    bl print1
und_code:
    .word 0xdeadc0de  /* 未定义指令 */
    bl print2

异常之软中断

  • 系统复位进入管理模式

  • 正常流程进入用户模式

    // M[4:0]=10000 为用户模式
    mrs r0, cpsr      /* 读出cpsr 读到r0 */
    /使用bic命令 bitclean 把低4位清零/
    //管理模式——BIT4本来就是1
    bic r0, r0, #0xf  /* 修改M4-M0为0b10000, 进入usr模式 */
    msr cpsr, r0
  • App运行在用户模式,不可访问硬件,需要进入特权模式.可以通过触发软中断

    命令 swi xxx

如何获得SWI 参数

  1. LR寄存器保存着下次的返回地址,所以对LR-4就能获得swi x这个指令的地址,所以读取这个地址就能获取到这个数值mark

  2. 代码如下:

    mov r4, lr
    
    sub r0, r4, #4
    bl printSWIVal
    
    void printSWIVal(unsigned int *pSWI)
    {
     puts("SWI val = ");
     printHex(*pSWI & ~0xff000000);
     puts("\n\r");
    }

中断框图

mark

  1. 某个中断信号如果使能了FIQ快中断==MODE寄存器==,则不会再去触发中断IRQ
  2. INTPEND指示了当前优先级最高的要处理的中断
  3. mask为屏蔽寄存器有些中断公用一个INTPEND,需要再去查询subsrcpend
  4. 两个中断挂起寄存器:源挂起寄存器(SRCPND)和中断挂起寄存器(INTPND)
  5. ==INTPEND==只对IRQ起作用,还有一个==INTOFFSET==也是一样,前者是bit位,后者指示哪个bit位

mark

  1. 注意仲裁器的 REQ0 的优先级总是最高并且 REQ5 的优先级总是最低

  2. 这里的中断优先级是有一个轮寻的概念,也就是排除REQ0REQ5之后,剩下的1,2,3,4,首先保持着1,2,3,4的绕尾形式.也就是当ARB_MODE=1的时候,可以指定当前最前面的是那个REQx,同时如果发生了某个REQ,则下次的轮训,该中断就在最后面了.比如当前发生了3,则下次的顺序是4,1,2,3,注意,这个自动变换也是需要ARB_MODE=1

    mark

快中断与中断

  • IRQ模式只能被FIQ模式打断,FIQ模式下谁也打不断。

  • 快中断的优先级?,应是没有这个东西

    同一时间只能有一个中断可以被设成快速中断

  • 清除中断标志需要从源头开始清除,因为先清除后端的时候,前端的标志会置位后端标志

流程设计

注意

进入异常之前应该先设置好异常的栈,韦东山的实例中是进入中断后设置异常的sp

保护现场

  • 硬件自动保存

    • 保存当前的cpsr到中断下的scpsr

    • 保存返回地址到LR寄存器,(某种异常模式的LR等于被中断的下一条指令的地址)它有可能是PC + 4有可能是PC + 8,到底是那种取决于不同的情况

  • 软件需要做的stmdb sp!, {r0-r12, lr}

    • 保存R0~R12寄存器

    • 保存LR寄存器,注意这个保存之后是退出中断的返回地址,因为ARM架构的原因,不同的中断异常有不同的返回地址,所以需要对这个LR先进行一些运算再保存.偏移地址如下:举个例子比如UDEF异常,只需要直接复制,而FIQ则需要保存LR+4

      BL      MOV PC, R14
      SWI     MOVS PC, R14_svc 
      UDEF    MOVS PC, R14_und 
      FIQ     SUBS PC, R14_fiq, #4 
      IRQ     SUBS PC, R14_irq, #4 
      PABT    SUBS PC, R14_abt, #4 
      DABT    SUBS PC, R14_abt, #8 

恢复现场

  • 恢复R0~R12,恢复LR,恢复scpsrCPSR,代码:ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */

程序设计

  • 初始化

    • 打开总中断开关,切换到用户模式,

          mrs r0, cpsr         /* 读出cpsr */
          bic r0, r0, #0xf     /* 修改M4-M0为0b10000, 进入usr模式 */
          bic r0, r0, #(1<<7)  /* 清除I位, 使能中断 */
          msr cpsr, r0
      
          /* 设置 sp_usr */
          ldr sp, =0x33f00000
    • 设置框图中的寄存器,外部中断注意设置管脚复用,边沿方式
      mark

  • 中断处理函数,先在IRQ判断是哪个INTPND,如果有sub源还需要进一步判断.外中断公用的,使用io值判断.EINTPEND这个也能判断

按键中断程序

按键IO如下

 *      eint0   GPF0
 *      eint2   GPF2
 *      eint11  GPG3
 *      eint19  GPG11

初始化程序

INTMSK&=~(_BIT0|_BIT2|_BIT5);//取消 eint0,eint2,eint8~23的mask
EINTMASK&=~(_BIT11|_BIT19);  //取消eint11,19的mask
//配置gpio为irq,双边触发
//eint0
EXTINT0|=(_BIT0|_BIT1|_BIT2);
//eint2
EXTINT0|=(_BIT8|_BIT9|_BIT10);
//eint11
EXTINT1|=(_BIT12|_BIT13|_BIT14);
//eint19
EXTINT2|=(_BIT12|_BIT13|_BIT14);
//GPF0,GPF2 
GPFCON&=~(_BIT0|_BIT1|_BIT4|_BIT5);
GPFCON|= (_BIT1|_BIT5);
//GPG3 GPG11
GPGCON&=~(_BIT6|_BIT7|_BIT22|_BIT23);
GPGCON|= (_BIT7|_BIT23);

中断判断

void interrupt_irq(void)
{
    uint32_t pend=INTOFFSET;

    if (pend == 0 || pend == 2 || pend == 5)  /* eint0,2,eint8_23 */
    {
        key_eint_irq(pend); /* 处理中断, 清中断源EINTPEND */
    }

    /* 清中断 : 从源头开始清 */
    SRCPND = (1<<pend);
    INTPND = (1<<pend);

}

void key_eint_irq(uint32_t irq)
{
    unsigned int val = EINTPEND; //如果是eint9//11 还需要判断这位
}

定时器中断

框图

mark

  1. 内部有一个16位的计数器TCNTn,以及一个比较值寄存器TCMPn,可以理解为影子寄存器
  2. 使用TCMPBn赋值到TCMPBn,使用TCNTBn初始化计数器TCNTn

初始化

  1. Write the initial value into TCNTBn and TCMPBn.
  2. Set the manual update bit of the corresponding timer. It is recommended that you configure the inverter on/off bit. (Whether use inverter or not).
  3. Set start bit of the corresponding timer to start the timer (and clear the manual update bit).

也就是说

  1. 初始值写入到 TCNTBn 和 TCMPBn 中

  2. 设置相应定时器的手动更新位

  3. 设置相应定时器的开始位来启动定时器(并且清除手动更新位)。如果定时器被强制停止,TCNTn 保持计数器值并且不会从 TCNTBn 重载。如果需要设置一个新值,执行手动更新。

    注意: 先设置TCON|=_BIT1;更新值,然后清除这一位,然后再开启定时器.必须要这么做

void TimerInit()
{
    INTMSK&=~(_BIT10);

    // 预分频=99
    TCFG0=99;
    // 1/16 clk
    TCFG1&=~(_BIT0|_BIT1|_BIT2|_BIT3);
    TCFG1|=(_BIT0|_BIT1);
    //clk=50M/100/16=31250

    TCNTB0=31250>>1;
    //TCMPB0
    TCON|=_BIT1;        //更新初值
    TCON&=~_BIT1;       //关闭手动更新
    TCON|=(_BIT0|_BIT3);// 开启定时器,同时设置自动重载
}
void TimerIrq(void)
{
    GPFDAT ^= (1<<6);
}

流程优化

  1. 中断处理使用注册函数,也就是说中断函数通过中断号查询一个数组去执行函数

  2. 注册函数会注册中断应该使用的函数,以及打开中断

    typedef void(*irq_func)(int);
    irq_func irq_array[32];
    
    void handle_irq_c(void)
    {
     /* 分辨中断源 */
     int bit = INTOFFSET;
    
     /* 调用对应的处理函数 */
     irq_array[bit](bit);
    
     /* 清中断 : 从源头开始清 */
     SRCPND = (1<<bit);
     INTPND = (1<<bit);  
    }
    
    void register_irq(int irq, irq_func fp)
    {
     irq_array[irq] = fp;
    
     INTMSK &= ~(1<<irq);
    }

汇编中的指令对齐

搜索下官方文档的索引.align,有如下描述,也就是有两种情况,对于ARM,表示的是末尾几个0,也就是2^x了.

For other systems, including ppc, i386 using a.out format, arm and strongarm, it is the number of low-order zero bits the location counter must have after advancement. For example ‘.align 3’ advances the location counter until it a multiple of 8. If the location counter is already a multiple of 8, no change is needed.

当在代码中定义了字符串之后,可能会出现代码指令非4字节对齐的情况,如下:reset的指令在30000045的位置,显然有问题,使用aligin来保持对齐

 //
 und_string:
       .string "undefined instruction exception-"
  reset:
 //-------------------------------------------------------------
 30000024 <und_string>:
  22 30000024:   65646e75    strvsb  r6, [r4, #-3701]!
  23 30000028:   656e6966    strvsb  r6, [lr, #-2406]!
  24 3000002c:   6e692064    cdpvs   0, 6, cr2, cr9, cr4, {3}
  25 30000030:   75727473    ldrvcb  r7, [r2, #-1139]!
  26 30000034:   6f697463    swivs   0x00697463
  27 30000038:   7865206e    stmvcda r5!, {r1, r2, r3, r5, r6, sp}^
  28 3000003c:   74706563    ldrvcbt r6, [r0], #-1379
  29 30000040:   2d6e6f69    stccsl  15, cr6, [lr, #-420]!
  30     ...
  31
  32 30000045 <reset>:
  33 30000045:   e3a00453    mov r0, #1392508928 ; 0x53000000
  • 使用align 4 之后对齐在3000,005032 30000050 <reset>:

  • 使用align 5之后 对齐在3000006030000060 <reset>:

    写一个demo测试一下,发现.align x对齐的是2^x字节,uboot对齐的是align 5,也就是32字节

    _start:
         .string "1234"
    _test_addr:
        ldr r0,[r2]
    反汇编如下    
    00000000 <_start>:
    00000005 <_test_addr>:    
    //---------------------------------------------------
    _start:
         .string "1234"
    .align 4 
    _test_addr:
        ldr r0,[r2] 
    反汇编如下
    00000000 <_start>:
    00000010 <_test_addr>:
    //---------------------------------------------------
    _start:
         .string "1234"
    .align 5 
    _test_addr:
        ldr r0,[r2] 
    反汇编如下
    00000000 <_start>:
    00000020 <_test_addr>:

中断向量表的优化

假设存在以下的跳转,如果hello这个函数在4kNand之外,使用bl指令还是在Nand或者Nor中跳转,是无法实际跳转到hello中的,所以要保证跳转到undef_irq是绝对跳转,或者后面使用ldr,pc=hello

_start: 
    b       reset
    b       undef_irq

undef_irq:
    ....
    ....bl  hello //跳转到一个c函数
    ....

所以这里的 跳转中断应该使用ldr pc,=undef_irq,实际上重定位后的代码的跳转都应该使用ldr

_start: 
    b       reset
    ldr     pc,=do_und

do_und:
    ....
    ....bl  hello //跳转到一个c函数
    ....

查看下这种写法的反汇编,程序是将地址存放到一个地址300000c0,然后去这个地址读出想要的pc值30000008.这个地址是在start.s的最后面

30000000 <_start>:
   7 30000000:   ea00000e    b   30000040 <reset>
   8 30000004:   e59ff0b4    ldr pc, [pc, #180]  ; 300000c0 <.text+0xc0>
   9
  10 30000008 <do_und>:
  
300000bc <halt>:
  64 300000bc:   eafffffe    b   300000bc <halt>
  65 300000c0:   30000008    andcc   r0, r0, r8

也就是说ldr pc,=do_und是从最后面的内存单元去读取pc值.如果是从Nand启动的时候,Start.S大于4K,ldr这个取址就会失败,所以希望ldr不要去程序的最后面读取pc值.所以我们将其放到前面的4k

.globl _start
_start: 
    b       reset
    ldr pc, _do_und

所以先在前面4k保存下这个跳转的地址,注意,这里是ldr pc,undef_addr表示的是取标号的内容

_start:
    b reset     //reset
    ldr pc,undef_addr   //未定义指令
    
undef_addr:
    .word do_und
do_und:

反汇编如下,可以看到是按照pc+4的地方去读

6 30000000 <_start>:
   7 30000000:   ea00000f    b   30000044 <reset>
   8 30000004:   e51ff004    ldr pc, [pc, #-4]   ; 30000008 <undef_addr>
   9
  10 30000008 <undef_addr>:
  11 30000008:   3000000c    andcc   r0, r0, ip
  12
  13 3000000c <do_und>:
  14 3000000c:   e3a0d30d    mov sp, #872415232  ; 0x34000000

最终的向量表

  1. 跳转使用ldr表示跳到链接地址
  2. 使用word保存地址是为了防止ldr取地址的地方超过4K.NAND启动不可访问
.globl _start
_start: 
    b       reset
    ldr pc, _undefined_instruction
    ldr pc, _software_interrupt
    ldr pc, _prefetch_abort
    ldr pc, _data_abort
    ldr pc, _not_used
    ldr pc, _irq
    ldr pc, _fiq

_undefined_instruction: .word undefined_instruction
_software_interrupt:    .word software_interrupt
_prefetch_abort:    .word prefetch_abort
_data_abort:        .word data_abort
_not_used:      .word not_used
_irq:           .word irq
_fiq:           .word fiq

Linux的中断处理

https://blog.csdn.net/linyangspring/article/details/39670449?utm_source=blogxgwz0

Linux不用FIQ,只用到了IRQ。但是我们有时候一个中断需要处理很长时间,那我们就需要占用IRQ模式那么长的时间吗?没有,linux在IRQ模式下只是简单的记录是什么中断,马上就切换回了SVC模式,换句话说,Linux的中断处理都是在SVC模式下处理的。那么中断号是怎么来的呢?在ARM上固死了,相应的中断号只有一个办法得到:查询irqs.h 。那我先用一个中断号注册一个中断处理程序,当中断发生的时候,Linux怎么知道是我这个中断号发生的中断呢?在处理中断的时候,先读取INTPND,根据需要再读取EINTPEND或SUBSRCPND计算出一个中断号,相应的处理算法在 get_irq_nr_base这个宏中。而且irqs.h中的中断号就是根据这个算法把每个中断算一下得来的。

在linux的中断处理子模块中,把中断的处理分为两部分,上半部和下半部。上半部运行在IRQ模式,置位I位,禁止IRQ中断,主要处理一些紧急的事情,比如记录中断号、清除中断源等。下半部运行在SVC模式,清零I位,允许IRQ中断,处理一些中断相关的非紧急事情,比如读取设备缓存等。

猜你喜欢

转载自www.cnblogs.com/zongzi10010/p/10023603.html