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 interrupt和GIC(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处理完了,可以将这个中断清理,
8.5GIC硬件原理
GIC 是连接外设中断和CPU的桥梁,也是多个CPU之间中断互联的通道,它负责检测、管理、分发中断;
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信号;
5)GICv2中断控制器
GIC-400包括两组寄存器
D系列:The Distributor registers(GICD_),包含中断设置和配置;
C系列:The CPU Interface registers(GICC_),包含CPU相关的特殊寄存器;
访问GIC-400寄存器:树莓派4b中GIC-400基地
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; }
精品文章推荐阅读: