面向未来:理解ARM处理器的新一代技术(下)

简介: 面向未来:理解ARM处理器的新一代技术

5.2GNU LD链接器

链接器Linker:是一个程序,将一个或多个编译器或汇编生成的目标文件,及依赖库,链接为一个可执行文件。

GNU Linker采用AT&T链接脚本语言;

链接脚本文件:包含ld程序链接的规则,其决定输出可执行文件的内存布局;

LD命令:arm64版本的连接器是aarch64-linux-gnu-ld

查看命令参数:

aarch64-linux-gnu-ld --help

LD命令的参数有很多,常用的如下:

$(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -Map xxx.map -o $(BUILD_DIR)/xxx.elf  $(OBJ_FILES)

常用参数:

  • -T: 指定连接脚本;
  • -Map:输出一个符号表文件;
  • -o: 输出最终可执行二进制文件;

基本概念

  • 输入段(input section),输出段(output section)
  • 每个段包括name和大小;

段的属性:

  • loadable:运行时会加载这些段内容到内存中;
  • allocatable:运行时不加载段的内容;

段的地址:

  • VMA(virtual memory address):虚拟地址,运行地址;
  • LMA(load memory address):加载地址
  • 通常ROM的地址为加载地址,而RAM地址为VMA;

链接脚本命令

Entry(symbol): 设置程序的入口函数;

GNU-AS程序入口点有以下几种:

  • a.使用-e参数;
  • b.使用ENTRY(symbol);
  • c.在.text的最开始的地方;
  • d.0地址;
  • INCLUDE filename:引入filename的链接脚本;
  • OUTPUT filename:输出二进制文件,类似命令行的"-o filename"
  • OUTPUT_FORMAT(bfd):输出BFD格式;
  • OUTPUT_ARCH(bfdarch): 输出处理器体系架构格式
OUTPUT_ARCH(aarch64)
ENTRY(_text)

赋值符号

符号可以像C语言一样赋值;

"."表示当前位置;

symbol = expression;
symbol += expression;
symbol -= expression;
symbol *= expression;
symbol /= expression;
symbol <<= expression;
symbol >>= expression;
symbol &= expression;
symbol |= expression;

号的引用

在C语言定义一个变量并初始化,编译器会分配一个符号,且在内存中分配空间存储;

在链接脚本中定义的变量,仅仅是在符号表里定义这个符号,没有分配存储空间;

xxx.ld
start_of_ROM = .ROM;
end_of_ROM = .ROM + sizeof(.ROM)

可以在每个段中设置一些符号,以方便C语言访问每个段的起始地址和结束地址;

C语言中,对链接脚本里定义的符号引用

extern char start_of_ROM[],end_of_ROM[];
char buf[4096];
memcpy(buf, start_of_ROM, end_of_ROM - start_of_ROM);//引用的是地址

SECTIONS命令

告诉链接器如何把输入段(input sections)映射到输出段(output sections),决定输出程序的内存布局;

输出section的描述符:

LMA加载地址

每个段都有VMA和LMA;

在输出段描述符中使用“AT”指定LMA, 如果没有指定,通常LMA=VMA;

构造一个基于ROM的映像文件,常常会设置输出端的虚拟地址和加载地址不一样;

SECTIONS
{
  .text 0x1000:{*(.text)_etext=.;}
  .mdata 0x2000:
  AT(ADDR(.text)+SIZEOF(.text))  //指定加载地址
  {
    _data=.;
    *(.data);
    _edata=.;  //_data和_edata只是一个符号,用来记录mdata段的VMA地址
  }
  .bss 0x3000;
  {
    _bstart=.;
    *(.bss)*(COMMON);
    -bend=.;
  }
}

mdata段的加载地址和链接地址不一样,因此程序初始化代码需要把data段内容复制到SDRAM中的虚拟地址中;

常见的内建函数(builtin functions)

  • ADDR(section):返回前面已经定义过的段的VMA地址;
  • ALIGN(n): 返回下一个与n字节对齐的地址;
  • SIZEOF(section):返回一个段的大小;
  • MAX/MIN(exp1,exp2):返回较大/较小值;

实例练习:设置代码段的LMA!=VMA

TEXT_ROM = 0X90000;
SECTIONS
{
  /*
   * -->location, current addr
   *  0x80000 is entrance address
   */
  . = 0x80000,
  /*
   * first instruction
   **/
  _text_boot = .;
  .text.boot : { *(.text.boot) }  
  _etext_boot = .;
  /*
   * code segment
   */
  _text = .;
  .text : AT(TEXT_ROM)
  { *(.text) }
  _etext = .;
  /*
   * readonly data segment
   */
  _rodata = .;
  .rodata : AT(ADDR(.rodata))
  { *(.rodata) }
  _erodata = .;
  /*
   * data segment
   */
  _data = .;
  .data : { *(.data) }
  _edata = .;
  /*
   * bss segment
   * 8bytes algine
   */
  . = ALIGN(0x8);
  _bss = .;
  .bss : { *(.bss*) } 
  _ebss = .;
  /*
   * alloc a page memory, store page table
   * aligne 4096
   */
  . = ALIGN(4096);
  init_pg_dir = .;
  . +=4096;
}

在启动代码,将代码段从LMA(ROM地址)拷贝到VMA(SDRAM地址)

master:
/*
 * copy code segment from rom(LMA) TO ram(VMA)
 */
  adr x0, TEXT_ROM
  adr x1, _text
  adr x2, _etext
1:
  ldr x4, [x0], #8
  str x4, [x1], #8
  cmp x1, x2
  b.cc 1b

六、GCC内嵌汇编补充

目的:

(1)优化,对特定代码进行优化;

(2)C语言需要访问某些特殊指令来实现特殊功能,比如内存屏障指令;

内嵌汇编两种模式:

基础内嵌汇编:不带参数;

扩展的内嵌汇编:C语言变量参数;

(1)基础内嵌汇编

格式:

  • asm关键字:表明是一个GNU扩展;
  • 修饰词(qualifiers)
  • volatile:基础内嵌汇编中,通常不需要;
  • inline:内敛,asm代码会尽可能小;

汇编代码块:

  • GCC编译器把内嵌汇编当成一个字符串;
  • GCC编译器不会去解析和分析内嵌汇编;
  • 多条汇编指令,需要使用“\n\t”换行;
  • GCC的优化器,可能乱序汇编指令,如果需要保持汇编指令的顺序,最好使用多个内嵌汇编的方式;

内核arch/arm64/include/asm/barrier.h文件

(2)扩展内嵌汇编

asm asm-qualifiers(
        Assembler Template;
        :outputOperands
        :inputOperands
        [:Clobbers])

格式:

  • asm关键字:表明是一个GNU扩展;
  • 修饰词(qualifiers):
  • volatile:用来关闭GCC优化;
  • inline: 内敛,减小汇编代码尺寸;
  • goto:在内嵌汇编里会跳转到C语言的label;

输出部:

  • 用于描述在指令部中可以被修改的C语言变量以及约束条件;
  • 每个约束通常以"="开头,接着的字母表示对操作类型的说明,然后是关于变量结合的约束;
“=+”+约束修饰符+变量

“=”表示被修饰的操作数具有可写属性;

“+”表示被修饰的操作数具有可读可写属性;

输出部可以是空的;

输入部:

  • 用来描述在指令部中,只读的C语言变量以及约束条件;
  • 输入部描述的参数,是只读属性,不要修改参数内容,因为GCC编译器假定输入部参数,在内嵌汇编前后是一致的;
  • 输入部中不能使用"=/+"约束条件,编译器会报错;
  • 输入部可以是空的;

损坏部:

“memory”告诉GCC内嵌汇编指令 改变了内存的值,强迫编译器在执行该汇编代码前,存储所有缓存的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序;

“cc”:表示内嵌汇编代码影响状态寄存器相关的标志位;

内核里的实例,arch/arm64/include/asm/barrier.h

扩展内嵌汇编指令部中的参数表示:

案例1,用内嵌汇编实现memcpy:

static void my_memcpy_asm_test(unsigned long src, unsigned long dst,unsigned long counter)
  {
      unsigned long tmp;
      unsigned long end = src+counter;
      asm volatile(
              "1: ldr %1, [%2], #8\n"
              "str %1, [%0], #8\n"
              "cmp %2, %3\n"
              "b.cc 1b"
              :"+r"(dst),"+r"(tmp),"+r"(src)   //output list, can write and read
              :"r"(end)                        //input list, readonly
              :"memory"
              );
  }

使用汇编符号名实现内嵌汇编:

static void my_memcpy_asm_test(unsigned long src, unsigned long dst,unsigned long counter)
  {
      unsigned long tmp;
      unsigned long end = src+counter;
      asm volatile(
              "1: ldr %[tmp], [%[src]], #8\n"
              "str %[tmp], [%[dst]], #8\n"
              "cmp %[src], %[end]\n"
              "b.cc 1b"
              :[dst]"+r"(dst),[tmp]"+r"(tmp),[src]"+r"(src)   //output list, can write and read
              :[end]"r"(end)                        //input list, readonly
              :"memory"
              );  
  }

内嵌汇编与宏结合

#define MY_OPS(ops,asm_ops) \
  static inline my_asm_##ops(unsigned long mask, void *p) \
  { \
      unsigned long tmp; \
      asm volatile( \
              "ldr %1, [%0]\n" \
              " "#asm_ops" %1, %1, %2\n" \
              "str %1,[%0]\n" \
              : "+r"(p), "+r"(tmp) \
              :"r"(mask) \
              : "memory" \
              ); \
  }
  MY_OPS(and, and)
  MY_OPS(or, orr)
  MY_OPS(andnot, bic)
  static void my_ops_test(void)
  {
      unsigned long p;
      p = 0xf;
      my_asm_and(0x2, &p);
      printk("test and:p=0x%x\n",p);
      p = 0x0;
      my_asm_or(0x3, &p);
      printk("test or:p=0x%x\n",p);
      p = 0x3;
      my_asm_andnot(0x2, &p);
      printk("test andnot:p=0x%x\n",p);
  }

技巧:

(1)使用了C语言宏中的“#”和“##”;

“#”:预处理器把这个参数转换为一个字符串;

“##”:是一种分隔链接方式,它的作用是先分隔,在进行强制链接;

(2)通过一个宏来实现多个类似的函数,这是linux内核常用的技巧;

实现读和写系统寄存器的宏

//equals    asm volatile("mrs %0,""#reg" :"=r"(_val)); _val;}
  #define read_sysreg(reg) ({ \
      unsigned long _val; \
      asm volatile("mrs %0,"#reg \
                   :"=r"(_val)); \
                   _val; \
  })
  #define write_sysreg(val, reg) ({\
          unsigned long _val=(unsigned long)val; \
          asm volatile("msr "#reg ",%0" \
          ::"rZ"(_val)); \
  })
  static test_sysregs(void)
  {
      unsigned long el; 
      el = read_sysreg(CurrentEL);
      printk("el=%d\n",el>>2);
      write_sysreg(0x10000, vbar_el1);
      printk("read vbar:0x%x\n",read_sysreg(vbar_el1));
  }

实现从汇编到C代码的跳转

static void test_asm_goto(int a)                                                                                                           
  {
      asm goto(
              "cmp %w0, 1\n"
              "b.eq %l[label]\n"
              :        //必须为空
              :"r"(a)
              :"memory"
              :label
              );  
      return 0;
  label:
      printk("%s: a=%d.\n",__func__,a);
  }

goto模板的输出部必须为空;

七、ARM64的异常处理之中断处理(以树莓派4采用的BCM2711芯片为例)

ARM64的中断属于异常模式的一种,对中断的处理流程跟上节的异常处理类似,走其中的一个分支;

树莓派4b的中断控制器分两种:传统的中断方式legacy interruptGIC(BCM2711支持GIC-400)

一个典型的ARM64中断处理流程:

7.1树莓派4b上的硬件资源

1.中断控制器:

  • GIC-400(默认)
  • 传统的中断控制器(legacy interrupt controller)

2.树莓派4b支持多种中断源:

  • ARM Core N: ARM Core本身的中断源,例如Core里的generic timer;
  • ARMC: 可以被VPU和CPU访问的中断源,例如mailbox;
  • ARM_LOCAL: 只能被CPU访问的中断源,例如本地timer等;

3.树莓派4b的legacy中断控制器

实例:以ARM Core的generic timer产生中断信号

7.2Cortex-A72支持4个ARM Core的generic timer

这里采用Nonsecure EL1 Physical Timer,演示中断处理过程;

查看ARMv8手册,第一个是EL1 TIMER的控制寄存器CNTP_CTL_EL0;

计数器寄存器CNTP_TVAL_EL0

Timer支持两种触发方式,参考armv8_D11.2.4;

7.3ARM_LOCAL寄存器有:

  • 4个IRQ_SOURCE寄存器,每个CPU一个,表示IRQ中断源状态;
  • 4个FIQ_SOURCE寄存器,每个CPU一个,表示FIQ中断源状态;
  • 4个TIMER_CNTRL寄存器,每个CPU一个,用来使能对应中断源;

1.查看BCM2711手册可知,ARM_LOCAL基地址为0x4c000000或0xff800000

对于IRQ_SOURCE寄存器,可以读取对应中断源状态

TIMER_CNTRL寄存器用来使能中断响应

7.4EL1的Nonsecure generic timer中断处理流程

初始化

  • 1.初始化timer,设置cntp_ctl_el0寄存器的enable域使能;
  • 2.给timer的TimeValue一个初值,设置cntp_tval_el0寄存器;
  • 3.打开树莓派b4中断控制器中和timer相关的中断,设置TIMER_CNTRL0寄存器的CNT_PNS_IRQ为1;
  • 4.打开PSTATE寄存器中的IRQ中断总开关;

异常中断处理

  • 5.Timer中断发生;
  • 6.跳转到异常向量表的el1_irq1函数;
  • 7.保存中断上下文(kernel_entry宏);
  • 8.跳转到中断处理函数;
  • 9.读取ARM_LOCAL中断状态寄存器IRQ_SOURCE0;
  • 10.判断中断源,是否为CNT_PNS_IRQ中断;
  • 11.如果是CNT_PNS_IRQ,重置TimerValue;

返回现场

  • 12.返回到el1_irq函数;
  • 13.恢复中断上下文;
  • 14.返回中断现场

中断源的判断:

以一个稍微复杂的中断源为例,根据树莓派4b BCM2711手册:

由上图可知,最多分三级:

  • 1.读SOURCEn中断寄存器;
  • 2.若SOURCEn[8]置位,继续读取PENDING2寄存器;
  • 3.若PENDING2[24]置位,则读PENDING0寄存器;
  • 4.若PENDING2[25]置位,则读PENDING1寄存器;

可见当中断源数量增加到一定程度,中断处理程序将会变得十分繁杂,GIC控制器就是为解决这个问题而生。

7.5中断现场(上下文)

1)中断上下文内容

中断发生瞬间,CPU状态包括:

PSTATE寄存器
PC值
SP值
X0~X30寄存器

Linux使用一个栈框数据结构来描述需要保存的中断现场;

2)保存中断现场

中断发生时,中断上下文保存到当前进程的内核栈里;

代码实现:

.macro kernel_entry
  sub sp, sp, #S_FRAME_SIZE
  /*
     保存通用寄存器x0~x29到栈框里pt_regs->x0~x29
   */
  stp x0, x1, [sp, #16 *0]
  stp x2, x3, [sp, #16 *1]
  stp x4, x5, [sp, #16 *2]
  stp x6, x7, [sp, #16 *3]
  stp x8, x9, [sp, #16 *4]
  stp x10, x11, [sp, #16 *5]
  stp x12, x13, [sp, #16 *6]
  stp x14, x15, [sp, #16 *7]
  stp x16, x17, [sp, #16 *8]
  stp x18, x19, [sp, #16 *9]
  stp x20, x21, [sp, #16 *10]
  stp x22, x23, [sp, #16 *11]
  stp x24, x25, [sp, #16 *12]
  stp x26, x27, [sp, #16 *13]
  stp x28, x29, [sp, #16 *14]
  /* x21: 栈顶 的位置*/
  add     x21, sp, #S_FRAME_SIZE
  mrs     x22, elr_el1
  mrs     x23, spsr_el1
  /* 把lr保存到pt_regs->lr, 把sp保存到pt_regs->sp位置*/
  stp     lr, x21, [sp, #S_LR]
  /* 把elr_el1保存到pt_regs->pc中
     把spsr_elr保存到pt_regs->pstate中*/
  stp     x22, x23, [sp, #S_PC]
  .endm

3)恢复中断现场

中断处理完成后,从内核栈中恢复中断现场;

代码实现:

.macro kernel_exit
  /* 从pt_regs->pc中恢复elr_el1,
     从pt_regs->pstate中恢复spsr_el1
     */
  ldp     x21, x22, [sp, #S_PC]           // load ELR, SPSR
  msr     elr_el1, x21                    // set up the return data
  msr     spsr_el1, x22
  ldp     x0, x1, [sp, #16 * 0]
  ldp     x2, x3, [sp, #16 * 1]
  ldp     x4, x5, [sp, #16 * 2]
  ldp     x6, x7, [sp, #16 * 3]
  ldp     x8, x9, [sp, #16 * 4]
  ldp     x10, x11, [sp, #16 * 5]
  ldp     x12, x13, [sp, #16 * 6]
  ldp     x14, x15, [sp, #16 * 7]
  ldp     x16, x17, [sp, #16 * 8]
  ldp     x18, x19, [sp, #16 * 9]
  ldp     x20, x21, [sp, #16 * 10]
  ldp     x22, x23, [sp, #16 * 11]
  ldp     x24, x25, [sp, #16 * 12]
  ldp     x26, x27, [sp, #16 * 13]
  ldp     x28, x29, [sp, #16 * 14]
  /* 从pt_regs->lr中恢复lr*/
  ldr     lr, [sp, #S_LR]
  add     sp, sp, #S_FRAME_SIZE           // restore sp
  eret
  .endm

5)实例代码:

在树莓派上实现generic timer, 关键代码

timer初始化

#include <asm/timer.h>
#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>
#include <timer.h>
#define HZ 250
#define NSEC_PER_SEC    6000000000L
static unsigned int val = NSEC_PER_SEC / HZ;
static int generic_timer_init(void)
{
  asm volatile(
    "mov x0, #1\n"
    "msr cntp_ctl_el0, x0"
    :
    :
    : "memory");
  return 0;
}
static int generic_timer_reset(unsigned int val)
{
  asm volatile(
    "msr cntp_tval_el0, %x[timer_val]"
    :
    : [timer_val] "r" (val)
    : "memory");
  return 0;
}
static void enable_timer_interrupt(void)
{
  writel(CNT_PNS_IRQ, TIMER_CNTRL0);
}
void timer_init(void)
{
  generic_timer_init();
  generic_timer_reset(val);
  enable_timer_interrupt();
}
void handle_timer_irq(void)
{
  generic_timer_reset(val);
  printk("Core0 Timer interrupt received\r\n");
}

中断服务程序:

#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>
void irq_handle(void)
{
  unsigned int irq = readl(ARM_LOCAL_IRQ_SOURCE0);
  switch (irq) {
  case (CNT_PNS_IRQ):
    handle_timer_irq();
    break;
  default:
    printk("Unknown pending irq: %x\r\n", irq);
  }
}

主应用程序:

void kernel_main(void)
{
  uart_init();
  init_printk_done();
  uart_send_string("Welcome BenOS!\r\n");
  printk("printk init done\n");
  //my_ldr_test();
  my_data_process();
  //my_cmp_test();
  //other_test();
  print_func_name(0x800880);
  //unsigned long val1 = 0,val2=0;
  //val1 = macro_test1(3,5);
  //val2 = macro_test2(3,5);
  print_memmap();
  my_memcpy_asm_test(0x80000, 0x100000, 32);
  my_memset_16bytes_asm(0x600000,0xaabbccdd,4096);
  //test_asm_goto(1);
  //trigger_alignment();
  printk("init done\n");
  timer_init(); //初始化timer
  raw_local_irq_enable();//启动timer
  my_ops_test();
  test_sysregs();
  int count=0;
  while (1) {
    //uart_send(uart_recv());
    printk("printk while...%ds\n",count++);
    delay_s(1);
  }
}

7.6在qemu上模拟结果如下:

八、ARM64的中断处理之GIC400实现(以树莓派4采用的BCM2711芯片为例)

8.1GIC的诞生背景

传统中断控制器,比如树莓派4b的legacy interrupt controller,具备

中断enable寄存器;

中断状态寄存器;

随着新业务的出现,比如:

  • 1.中断源变得越来越多;
  • 2.不同类型的中断出现,比如多核间中断,中断优先级,软中断等;
  • 3.支持虚拟化;

传统中断控制器已经无法满足需求,由此GIC控制器应运而生;

8.2GIC的版本号

GIC演进的版本号如下表

目前最新版本是GIC500/600,比如最新的高端手机大多支持,而在传统嵌入式系统中,大多GIC400就够用,比如这里以树莓派;

8.3GIC支持的中断类型

  • SGI: 软件产生的中断(Software Generated Interrupt),用于给其他CPU核发送中断信号;
  • PPI: 私有外设中断(Private Perpheral Interrupt),该中断是某个指定的CPU独有的;
  • SPI: 共享外设中断(Shared Peripheral Interrupt),所有CPU都可以访问这个中断;
  • LPI: 本地特殊外设中断(Locality-specific Peripheral Interrupt),GICv3新增的中断类型,基于消息传递的中断类型

8.4GIC的中断状态

中断的处理流程是:分发器把收集来的中断先缓存,依次把优先级最高的中断请求送往CPU接口,CPU读取一个中断,其实就是读取接口的一个寄存器,只不过这个寄存器存放的是中断号,此时中断的状态由pending转为active,CPU处理完了以后,将中断号写入GIC的接口,告诉GIC处理完了,可以将这个中断清理,

640.jpg

8.5GIC硬件原理

GIC 是连接外设中断和CPU的桥梁,也是多个CPU之间中断互联的通道,它负责检测、管理、分发中断;

640.jpg

1)GIC结构

上图摘自GIC400官方手册,GIC可以做到

1.使能或禁止中断;

2.把中断分组到FIQ或者IRQ;

3.多核系统中,可以将中断分配到不同CPU上;

4.设置GIC给到CPU的触发方式(不等于外设的触发方式);

5.支持虚拟化扩展;

2) GIC功能

由上图知,GIC按功能,划分为两部分:仲裁分发和CPU接口;

3)分发器distributor

主要作用是,检测各个中断源的状态,控制各个中断源的行为,分发各个中断源产生的中断事件到一个或多个CPU接口。主要功能包括:

  • (1) 使能或禁止中断,分发器对中断的控制分两级别,一个是全局中断控制(GIC_DIST_CTRL),一个是针对具体中断源控制(GIC_DIST_ENABLE_CLEAR),
  • (2)控制优先级;
  • (3)将仲裁后的最高优先级中断事件,分发给一个或多个CPU接口;
  • (4)中断属性设定,比如触发方式等;

4)CPU接口

(1)禁止或使能CPU接口向相应的CPU提交中断事件;对于ARM核,中断信号线是nIRQ或nFIQ,若禁止GIC中断,即使分发器分发了一个事件到CPU接口,也不会提交nIRQ或nFIQ信号给CPU;

(2)ackowledging中断;ARM核一旦应答了中断,分发器就会把该中断状态从pending改为active, 若没有后续pending中断,CPU接口会deassert nIRQ或nFIQ信号线;若有后续中断,CPU接口将中断状态改为pending and active, 此时依然保持nIRQ或nFIQ信号的asserted状态;

(3)中断处理完毕;ARM核处理完中断,回向CPU接口的寄存器写EOI命令,分发器将当前中断状态改为deactive,同时也可以将其他pending的中断向CPU接口提交;

(4)设定优先级掩码;可以屏蔽较低优先级中断,使其不同时给ARM核;

(5)设定中断抢占策略;

(6)始终选定最高优先级中断事件,提交给ARM核;

一个完整的中断处理过程时序图(摘自GIC400手册B1):

假定:

a.都是电平触发;

b.都是共享外设中断;

c.M/N信号都配置为同一个CPU的为FIQEn中断;

d.N信号优先级高于M信号;

640.jpg

5)GICv2中断控制器

GIC-400包括两组寄存器

D系列:The Distributor registers(GICD_),包含中断设置和配置;

C系列:The CPU Interface registers(GICC_),包含CPU相关的特殊寄存器;

访问GIC-400寄存器:树莓派4b中GIC-400基地

640.jpg

GIC-400初始化流程

  • (1)设置distributor和CPU interface寄存器组的基地址;
  • (2)读取GICD_TYPER寄存器,计算当前GIC最大支持多少个中断源;
  • (3)初始化distributor:
a.disable distributor;
b.设置中断分组;
b.设置SPI中断的路由;
c.设置SPI中断的触发类型;
d.disactive和disable所有中断源;
e.enable distributor;
  • (4)初始化CPU Interface:
a.设置GIC_CPU_PRIMASK,设置中断优先级mask level;
b. enable CPU interface;

相关寄存器说明

1.设置分组

group0:安全中断,由nFIQ驱动

group1:非安全中断,由nIRQ驱动

注册中断

  • (1) 初始化外设;
  • (2)查找该外设的中断在GIC-400的中断号,例如PNS timer中断号为30;
  • (3)设置GIC_DIST_ENABLE_SET寄存器来enable这个中断号;
  • (4)打开设备相关的中断,例如树莓派的generic timer,需要打开ARM_LOCAL寄存器组中的TIMER_CNTRL0寄存器中相应enable位;
  • (5)打开CPU的PSTATE中I位;

查树莓派手册知,PNS timer中断号为30

中断响应

  • 1.中断触发;
  • 2. 跳转异常向量表;
  • 3. 跳转到GIC中断函数里,gic_handle_irq();
  • 4. 读取GICC_IAR寄存器,获取中断号;
  • 5. 根据中断号来进行相应中断处理,例如,若读取中断号为30,说明是PNS的generic timer,跳转到generic timer处理函数;

读取中断号;

中断处理完成,写回EOI:

timer部分核心代码:

#include <asm/timer.h>
#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>
#include <timer.h>
#define HZ 250
#define NSEC_PER_SEC    8000000000L
static unsigned int val = NSEC_PER_SEC / HZ;
static int generic_timer_init(void)
{
  asm volatile(
    "mov x0, #1\n"
    "msr cntp_ctl_el0, x0"
    :
    :
    : "memory");
  return 0;
}
static int generic_timer_reset(unsigned int val)
{
  asm volatile(
    "msr cntp_tval_el0, %x[timer_val]"
    :
    : [timer_val] "r" (val)
    : "memory");
  return 0;
}
static void enable_timer_interrupt(void)
{
  writel(CNT_PNS_IRQ, TIMER_CNTRL0);
}
void timer_init(void)
{
  generic_timer_init();
  generic_timer_reset(val);
  gicv2_unmask_irq(GENERIC_TIMER_IRQ);
  //enable_timer_interrupt();
}
void handle_timer_irq(void)
{
  generic_timer_reset(val);
  printk("Core0 Timer interrupt received\r\n");
}

irq部分核心代码:

#include <arm-gic.h>
#include "io.h"
#include <asm/irq.h>
struct gic_chip_data {
  unsigned long raw_dist_base;
  unsigned long raw_cpu_base;
  struct irq_domain *domain;
  struct irq_chip *chip;
  unsigned int gic_irqs;
};
#define gic_dist_base(d) ((d)->raw_dist_base)
#define gic_cpu_base(d) ((d)->raw_cpu_base)
#define ARM_GIC_MAX_NR 1
static struct gic_chip_data gic_data[ARM_GIC_MAX_NR];
/* IRQs start ID */
#define HW_IRQ_START 16
static unsigned long gic_get_dist_base(void)
{
  struct gic_chip_data *gic = &gic_data[0];
  return gic_dist_base(gic);
}
static unsigned long gic_get_cpu_base(void)
{
  struct gic_chip_data *gic = &gic_data[0];
  return gic_cpu_base(gic);
}
static void gic_set_irq(int irq, unsigned int offset)
{
  unsigned int mask = 1 << (irq % 32);
  writel(mask, gic_get_dist_base() + offset + (irq / 32) * 4);
}
void gicv2_mask_irq(int irq)
{
  gic_set_irq(irq, GIC_DIST_ENABLE_CLEAR);
}
void gicv2_unmask_irq(int irq)
{
  gic_set_irq(irq, GIC_DIST_ENABLE_SET);
}
void gicv2_eoi_irq(int irq)
{
  writel(irq, gic_get_cpu_base() + GIC_CPU_EOI);
}
static unsigned int gic_get_cpumask(struct gic_chip_data *gic)
{
  unsigned long base = gic_dist_base(gic);
  unsigned int mask, i;
  for (i = mask = 0; i < 32; i += 4) {
    mask = readl(base + GIC_DIST_TARGET + i);
    printk("mask:0x%x\n",mask);
    mask |= mask >> 16;
    mask |= mask >> 8;
    printk("----irq[%d],mask:0x%x\n",i,mask);
    if (mask)
      break;
  }
  return mask;
}
static void gic_dist_init(struct gic_chip_data *gic)
{
  unsigned long base = gic_dist_base(gic);
  unsigned int cpumask;
  unsigned int gic_irqs = gic->gic_irqs;
  int i;
  /* 关闭中断*/
  writel(GICD_DISABLE, base + GIC_DIST_CTRL);
  unsigned int cpu_group=0;
  for (i = 0; i < 32; i += 4) {
    cpu_group = readl(base + GIC_DIST_IGROUP+i);
    printk("reg[%d],cpu_group:0x%x\n",i/4,cpu_group); //default for group0
  }
  /* 设置中断路由:GIC_DIST_TARGET
   *
   * 前32个中断怎么路由是GIC芯片固定的,因此先读GIC_DIST_TARGET前面的值
   * 然后全部填充到 SPI的中断号 */
  cpumask = gic_get_cpumask(gic);
  cpumask |= cpumask << 8;
  cpumask |= cpumask << 16;
  printk("----cpumask:0x%x\n",cpumask);
  for (i = 32; i < gic_irqs; i += 4)
    ;//writel(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);
  /* Set all global interrupts to be level triggered, active low */
  for (i = 32; i < gic_irqs; i += 16)
    //writel(GICD_INT_ACTLOW_LVLTRIG, base + GIC_DIST_CONFIG + i / 4);
    writel(0x1, base + GIC_DIST_CONFIG + i / 4);
  /* Deactivate and disable all 中断(SGI, PPI, SPI).
   *
   * 当注册中断的时候才 enable某个一个SPI中断,例如调用gic_unmask_irq()*/
  for (i = 0; i < gic_irqs; i += 32) {
    writel(GICD_INT_EN_CLR_X32, base +
        GIC_DIST_ACTIVE_CLEAR + i / 8);
    writel(GICD_INT_EN_CLR_X32, base +
        GIC_DIST_ENABLE_CLEAR + i / 8);
  }
  /*打开SGI中断(0~15),可能SMP会用到*/
  writel(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);
  /* 打开中断:Enable group0 and group1 interrupt forwarding.*/
  writel(GICD_ENABLE, base + GIC_DIST_CTRL);
}
void gic_cpu_init(struct gic_chip_data *gic)
{
  int i;
  unsigned long base = gic_cpu_base(gic);
  unsigned long dist_base = gic_dist_base(gic);
  /*
   * Set priority on PPI and SGI interrupts
   */
  for (i = 0; i < 32; i += 4)
    //writel(0xa0a0a0a0,dist_base + GIC_DIST_PRI + i * 4 / 4);
    writel(0x30,dist_base + GIC_DIST_PRI + i * 4 / 4);
  //writel(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
  writel(0x80, base + GIC_CPU_PRIMASK);
  writel(GICC_ENABLE, base + GIC_CPU_CTRL);
}
void gic_handle_irq(void)
{
  struct gic_chip_data *gic = &gic_data[0];
  unsigned long base = gic_cpu_base(gic);
  unsigned int irqstat, irqnr;
  do {
    irqstat = readl(base + GIC_CPU_INTACK);
    irqnr = irqstat & GICC_IAR_INT_ID_MASK; //get lower 9bits
    if (irqnr == GENERIC_TIMER_IRQ)
      handle_timer_irq();
    gicv2_eoi_irq(irqnr); //write eoi
  } while (0);
}
int gic_init(int chip, unsigned long dist_base, unsigned long cpu_base)
{
  struct gic_chip_data *gic;
  int gic_irqs;
  int virq_base;
  gic = &gic_data[chip];
  gic->raw_cpu_base = cpu_base;
  gic->raw_dist_base = dist_base;
  /* readout how many interrupts are supported*/
  gic_irqs = readl(gic_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
  gic_irqs = (gic_irqs + 1) * 32;
  if (gic_irqs > 1020)
    gic_irqs = 1020;
  gic->gic_irqs = gic_irqs;
  printk("%s: cpu_base:0x%x, dist_base:0x%x, gic_irqs:%d\n",
      __func__, cpu_base, dist_base, gic->gic_irqs);
  gic_dist_init(gic);
  gic_cpu_init(gic);
  return 0;
}


精品文章推荐阅读:

相关文章
|
2月前
|
存储 机器学习/深度学习 人工智能
嵌入式中一文搞懂ARM处理器架构
嵌入式中一文搞懂ARM处理器架构
44 1
|
2月前
|
人工智能 安全 数据安全/隐私保护
叫好不叫座?Arm、英特尔、AMD 等 5 位技术大咖畅聊机密计算技术
机密计算作为一项新兴技术,为我们如何安全处理和计算敏感数据提供了新的视角和解决方案。
|
3月前
|
存储 缓存 物联网
DP读书:鲲鹏处理器 架构与编程(二)服务器与处理器——高性能处理器的并行组织结构、ARM处理器
DP读书:鲲鹏处理器 架构与编程(二)服务器与处理器——高性能处理器的并行组织结构、ARM处理器
255 0
|
4月前
|
缓存 算法
内存系列学习(七):ARM处理器的快速上下文切换技术
内存系列学习(七):ARM处理器的快速上下文切换技术
55 0
|
4月前
|
存储 缓存 Linux
内存系列学习(六):ARM处理器存储访问一致性问题
内存系列学习(六):ARM处理器存储访问一致性问题
84 0
|
4月前
|
存储 算法 Linux
内存系列学习(五):ARM处理器中的Cache和Write Buffer
内存系列学习(五):ARM处理器中的Cache和Write Buffer
75 0
内存系列学习(五):ARM处理器中的Cache和Write Buffer
|
4月前
|
存储 缓存 Linux
内存系列学习(三):ARM处理器地址变换过程
内存系列学习(三):ARM处理器地址变换过程
64 0
|
4月前
|
存储 缓存 算法
内存系列学习(二):ARM处理器中CP15协处理器
内存系列学习(二):ARM处理器中CP15协处理器
53 0
|
6月前
|
存储 NoSQL 安全
面向未来:理解ARM处理器的新一代技术(上)
面向未来:理解ARM处理器的新一代技术
面向未来:理解ARM处理器的新一代技术(上)
|
1月前
|
数据处理 编译器 数据库
x64 和 arm64 处理器架构的区别
x64 和 arm64 处理器架构的区别
71 0