查看64位多核 MIPS 异常和中断内核代码分析 (1)的源代码
←
64位多核 MIPS 异常和中断内核代码分析 (1)
跳转到:
导航
,
搜索
因为以下原因,你没有权限编辑本页:
您刚才请求的操作只有这个用户组中的用户才能使用:
用户
您可以查看并复制此页面的源代码:
== 高优先级例外入口初始化 == 本文不涉及 Bootloader,因此 Reset 例外入口处的 Bootloader 我们就不讨论了。 多核环境下,所有核默认指向的例外入口基地址都是一样的,尽管可以修改各自核内部的 EBase 寄存器来改变例外入口基地址,但我们此处只讨论所有核运行在对称多处理模式(SMP) 下的情形,即所有核都使用同样的例外入口 <br><br> === Cache Error 例外入口 === 先看看 Cache Error 时,Linux Kernel 都做了什么 <br><br> ==== Loongson 2E Cache Error ==== Cache Error 例外入口初始化,位于: <source lang=c> [arch/mips/mm/c-r4k.c] void __init r4k_cache_init() { ...... set_uncached_handler(0x100, &except_vec2_generic, 0x80); ...... } </source> 因为 cache 错误时可以 cache 的 KSEG0 段不能用了,则 cache 错误例外处理函数位于 KSEG1 之0xFFFF FFFF A000 0000 + 0x100 处,长度最大为128 Bytes(32 条指令),处理函数为 except_vec2_generic,定义于: <source lang=c> [arch/mips/mm/cex-gen.S] /* * Game over. Go to the button. Press gently. Swear where allowed by * legislation. */ LEAF(except_vec2_generic) .set noreorder .set noat .set mips0 /* * This is a very bad place to be. Our cache error * detection has triggered. If we have write-back data * in the cache, we may not be able to recover. As a * first-order desperate measure, turn off KSEG0 cacheing. */ mfc0 k0,CP0_CONFIG li k1,~CONF_CM_CMASK and k0,k0,k1 ori k0,k0,CONF_CM_UNCACHED # 改变 KSEG0 为 Uncached mtc0 k0,CP0_CONFIG /* Give it a few cycles to sink in... */ nop nop nop j cache_parity_error nop END(except_vec2_generic) </source> 发生 Cache Error 后,Cache 已不再可信,因此原 KSEG0 不能再使用 Cache,首先改变其为 Uncached,然后进入真正的处理函数 cache_parity_error: <source lang=c> [arch/mips/kernel/traps.c] asmlinkage void cache_parity_error(void) { const int field = 2 * sizeof(unsigned long); unsigned int reg_val; /* For the moment, report the problem and hang. */ printk("Cache error exception:\n"); printk("cp0_errorepc == %0*lx\n", field, read_c0_errorepc()); reg_val = read_c0_cacheerr(); printk("c0_cacheerr == %08x\n", reg_val); printk("Decoded c0_cacheerr: %s cache fault in %s reference.\n", reg_val & (1<<30) ? "secondary" : "primary", reg_val & (1<<31) ? "data" : "insn"); printk("Error bits: %s%s%s%s%s%s%s\n", reg_val & (1<<29) ? "ED " : "", reg_val & (1<<28) ? "ET " : "", reg_val & (1<<26) ? "EE " : "", reg_val & (1<<25) ? "EB " : "", reg_val & (1<<24) ? "EI " : "", reg_val & (1<<23) ? "E1 " : "", reg_val & (1<<22) ? "E0 " : ""); printk("IDX: 0x%08x\n", reg_val & ((1<<22)-1)); #if defined(CONFIG_CPU_MIPS32) || defined(CONFIG_CPU_MIPS64) if (reg_val & (1<<22)) printk("DErrAddr0: 0x%0*lx\n", field, read_c0_derraddr0()); if (reg_val & (1<<23)) printk("DErrAddr1: 0x%0*lx\n", field, read_c0_derraddr1()); #endif panic("Can't handle the cache error!"); } </source> 事实上,当出现 Cache Error 时,系统已经没法修复,只能打出一些可供判断的信息后 panic :( 另外 set_uncached_handler 定义于: <source lang=c> [arch/mips/kernel/traps.c] /* * Install uncached CPU exception handler. * This is suitable only for the cache error exception which is the only * exception handler that is being run uncached. */ void __cpuinit set_uncached_handler(unsigned long offset, void *addr, unsigned long size) { unsigned long uncached_ebase = CKSEG1ADDR(ebase); if (!addr) panic(panic_null_cerr); memcpy((void *)(uncached_ebase + offset), addr, size); } </source> 其中 CKSEG1ADDR 的宏用于将虚址 ebase 转化为对应的 uncached 段的虚址 0xFFFF FFFF A000 0000。可以看到这个函数负责把例外处理函数 except_vec2_generic 复制到 Cache Error 例外的入口 0xFFFF FFFFF A000 0100 处,即物理地址 0x100 处 ==== Cavium Octeon Cache Error ==== Cavium Octeon 的 Cache 错误处理和 2E 大同小异,只是处理过程多了些鲁棒性 其Cache Error 例外入口初始化,位于: <source lang=c> [arch/mips/mm/c-octeon.c] void __cpuinit octeon_cache_init(void) { extern unsigned long ebase; extern char except_vec2_octeon; memcpy((void *)(ebase + 0x100), &except_vec2_octeon, 0x80); octeon_flush_cache_sigtramp(ebase + 0x100); ...... } </source> 将例外处理函数 except_vec2_octeon 复制到 ebase + 0x100 处,在 Cavium 上 ebase 为: <source lang=c> [arch/mips/cavium-octeon/setup.c] uint32_t ebase = read_c0_ebase() & 0x3ffff000 </source> 实际就是 EBase 寄存器中的值,默认情形下,ebase = 0x8000 0000,ebase + 0x100 就是 0x8000 0180,即物理地址 0x100 处。因为使用 KSEG0 的虚拟地址访问,数据会被缓存,因此为了及时生效到内存,还要 flush 一下 cache。 另外在 64 位的处理器上使用 32 位的地址访问,处理器会自动将其扩展为 64 位,规则就是 32位高位符号扩展。0x8000 0180 最高位为 1,则会被扩展为 0xFFFF FFFF 8000 0180; 0x0000 0180 则会被扩展为 0x0000 0000 0000 0180 其中,Octeon 的 Cache Error 例外处理函数 except_vec2_octeon 定义于: <source lang=c> [arch/mips/mm/cex-oct.S] /* * Handle cache error. Indicate to the second level handler whether * the exception is recoverable. */ LEAF(except_vec2_octeon) /* due to an errata we need to read the COP0 CacheErr (Dcache) * before any cache/DRAM access */ rdhwr k0, $0 /* get core_id */ PTR_LA k1, cache_err_dcache sll k0, k0, 3 PTR_ADDU k1, k0, k1 /* k1 = &cache_err_dcache[core_id] */ dmfc0 k0, CP0_CACHEERR, 1 sd k0, (k1) dmtc0 $0, CP0_CACHEERR, 1 # 把 DCache_Error 寄存器的值存入 cache_err_dcache[core_id] 数组 /* check whether this is a nested exception */ mfc0 k1, CP0_STATUS andi k1, k1, ST0_EXL beqz k1, 1f nop j cache_parity_error_octeon_non_recoverable nop /* exception is recoverable */ 1: j handle_cache_err nop END(except_vec2_octeon) </source> 回顾一下,例外发生时 CPU 会将 STATUS[EXL] 置为 1,但是在 Cache Error 例外出现时,EXL 不会被置为 1,CPU 而是将 STATUS[ERL] 置为 1,这个意味着: I. CPU 自动进入内核模式(忽略 STATUS[KSU] 位),禁止中断(忽略 STATUS[IE] 位) II. eret 指令使用 CP0_ErrorEPC 代替 CP0_EPC 作为例外返回地址 III. Kuseg 和 xkuseg 被改为 unmapped, uncached 的区域,以便在 cache 不能用时,内存空间能正常访问 貌似可恢复 Cache Error 的处理过程如下: 访问地址 VA ---> 第一次发生 Cache Error 例外 ---> 进入 except_vec2_octeon,STATUS[ERL] = 1 ---> CP0_STATUS & ST0_EXL 为 0 ---> 进入 handle_cache_err---> SAVE_ALL 保存上下文后,KMODE 清除 ERL 位 ---> 进入貌似 Cache Error 可恢复的处理函数cache_parity_error_octeon_recoverable ---> 打出CP0_ICache_Error/CP0_DCache_Error 和 CP0_ErrorEPC ---> 执行 eret 指令,例外返回到 ErrorEPC 指向的地址,即刚刚访问出错的地址 VA 再试一下 错误不可恢复的情形: 嵌套异常,即在已有的异常处理过程中,EXL 还没有被清除,却发生了 Cache Error,此时进入except_vec2_octeon,STATUS[ERL] = 1 --->CP0_STATUS & ST0_EXL 为 1 ---> 最终进入cache_parity_error_octeon_non_recoverable,打印CP0_ICache_Error/CP0_DCache_Error 和 CP0_ErrorEPC 后,直接 panic <source lang=c> /* We need to jump to handle_cache_err so that the previous handler * can fit within 0x80 bytes. We also move from 0xFFFFFFFFAXXXXXXX * space (uncached) to the 0xFFFFFFFF8XXXXXXX space (cached). */ LEAF(handle_cache_err) .set push .set noreorder .set noat SAVE_ALL KMODE # clear EXL, ERL, set kernel mode bit jal cache_parity_error_octeon_recoverable nop j ret_from_exception nop .set pop END(handle_cache_err) </source> [arch/mips/mm/c-octeon.c] <source lang=c> asmlinkage void cache_parity_error_octeon_recoverable(void) { cache_parity_error_octeon(0); } asmlinkage void cache_parity_error_octeon_non_recoverable(void) { cache_parity_error_octeon(1); } static void cache_parity_error_octeon(int non_recoverable) { unsigned long coreid = cvmx_get_core_num(); uint64_t icache_err = read_octeon_c0_icacheerr(); pr_err("Cache error exception:\n"); pr_err("cp0_errorepc == %lx\n", read_c0_errorepc()); if (icache_err & 1) { pr_err("CacheErr (Icache) == %llx\n", (unsigned long long)icache_err); write_octeon_c0_icacheerr(0); } if (cache_err_dcache[coreid] & 1) { pr_err("CacheErr (Dcache) == %llx\n", (unsigned long long)cache_err_dcache[coreid]); cache_err_dcache[coreid] = 0; } if (non_recoverable) panic("Can't handle cache error: nested exception"); } </source> <br><br> === TLB Refill 例外入口 === 非启动模式 (BEV=0) 下,MIPS64 R1上,64 位地址空间的 TLB Refill 入口在物理地址 0x80 处,其空间可以放一个 32 条指令的处理函数; 32 位地址空间的 TLB Refill 的 入口在物理地址 0x0 处,大小是 0x80,也可以放一个 32 条指令的处理函数;MIPS64 R2 默认与 R1 的入口一样,但其多了一个 cp0_ebase 寄存器,可以设置异常的基地址。 至于什么时候是 64 地址空间的访问什么时候又是 32 位地址空间访问,MIPS64 使用 cp0_status 的 KX, SX, UX 位来区分,以下情形都是 64 位地址模式,否则就是 32 位地址模式: KX = 1 时,访问 Kernel Address Space UX = 1 时,访问 Supervisor Address Space UX = 1 时,访问 User Address Space Loongson 2E 没有实现这个 KX/SX/UX 对地址空间的区分,因此其入口始终为 0xFFFF FFFF 8000 0000 Cavium Octeon 实现标准的 MIPS64 R2,但其通过 bootloader 设置 cp0_ebase 使其所有异常的基址变为 0xFFFF FFFF 8000 1000,从而 TLB Refill 用 0xFFFF FFFF 8000 1080 的入口 当访问一个需要经过 TLB 的虚拟地址 (mapped address) 时,如果在 TLB 中没找到相应的映射 (TLB Miss),同时原始 cp0_status[EXL] 为 0,才会进入这个 TLB Refill 入口;如果原来 cp0_status[EXL] 为 1,则表示 CPU 已经处于例外过程中,此时再发生找不到 TLB 项 (TLB Miss) 的情形,应该进入另外一个入口,以免 TLB Refill 嵌套。对于其它 EXL 为 1 的情形,不管在什么例外环境下出现 TLB Miss 的情形,都不会进入 TLB Refill 入口 比如访问 mapped address VA1,原来 EXL 为 0,无对应 TLB 项 (TLB Miss),进入这个入口,执行 TLB Refill 处理函数,注意,在 TLB Refill 处理函数中是不会去清除 EXL 位的,则此时如果再发生 mapped address 无对应 TLB 项 (TLB Miss)的情形,说明 TLB Refill 处理函数已经不能处理 VA1 对应 TLB 项的重填,得由另外功能更全面的处理函数善待之。更精确地,因为此时 EXL 已经为 1,EPC 不会被更新,还是原来访问 VA1 引起异常的地方,于是换个入口,进其他例外入口,最终由 TLB Invalid 异常的处理函数 handle_tlbl/handle_tlbs 来处理。 注意一下,TLB Invalid 异常的只在访问 VA1 时,对应 TLB 项的有效位 V 为 0 时才会触发;而 TLB Refill 是在 VA1 在 TLB 里没有对应项时触发,因此尽管 EXL 为 1 时,发生的 TLB Refill 异常同样进入 TLB Invalid 异常的处理函数,但还是能区分到底是 TLB Refill 还是 TLB Invalid,只要在处理函数里检查一下 TLB 里有没有 VA1 对应的项即可区分。 位于这些入口处 TLB Refill 处理函数,是由位于 arch/mips/mm/tlbex.c 中的 build_r4000_tlb_refill_handler() 在 kernel 初始化阶段动态生成,然后写到这些入口处的。至于为何要采取这种方式,主要是 MIPS 各种平台太多,如果根据用户的静态配置生成相应的 TLB Refill Handler,要照顾的情形太多,使用通常的条件编译方式已经不能满足要求了。关于这个动态生成过程后面的章节详细讨论。为分析这些动态生成的 Handler,我们可以在内核里把他们直接 dump 出来,具体操作过程,可以参考拙文:<dump MIPS TLB refill handler> ==== MIPS64 虚拟地址空间 ==== CN38xx实现 49 bit 虚拟地址,其 segbit 为 49 [[文件:Mips64-mem-map.png]] 其中内核空间是 xkphys 和 xkseg;其中 xkphys 是固定映射,不经 TLB (unmapped) 用户空间是 xuseg <br><br> ==== EXL=0 时 TLB Refill 的处理 ==== ===== Cavium Octeon ===== 这个的处理函数,在 Cavium Octeon 上位于 0xFFFF FFFF 8000 1080 处,但往往这个处理函数都会超过 0x80 字节,所以动态生成过程就使用原来 32 位的入口空间再放一部分代码,在 64 位下,这个 32 位的 TLB Refill 入口空间是不用的: <source lang=c> 0xFFFF FFFF 8000 1000: c: 07410003 bgez k0,1c <0x1c> # badvaddr >= 0,即 badvaddr 低 40 位以上有货,最高位不为 1,即在 0x0 ~ 0x7FFF FFFF FFFF FFFF (xuseg or xsseg) 中时则跳转。一句话就是去处理位于 xuseg or xsseg 的异常地址 10: 3c1b817c lui k1,0x817c # 位于延迟槽,总是被执行。0xFFFF FFFF 817c 0000 #badvaddr低 40 位以上不为 0 且不位于 xuseg 或 xsseg 中,又走 TLB,则其应为位于 xkseg,范围为 0xC000 0000 0000 0000 ~ 0xFFFF FFFF FFFF FFFF。一句话就是去处理位于 xkseg 中的地址 14: 10000024 b a8 <0xa8> ----->@@@ k1 = 0xFFFF FFFF 817B E000, it's swapper_pg_dir, 他是内核使用的除模块以外的 PGD 基地址,内核模块使用 18: 277be000 addiu k1,k1,-8192 # 位于延迟槽,总是被执行。0xFFFF FFFF 817c 0000 - 8192 = 0xFFFF FFFF 817B E000 #badvaddr 低 40 位以上不为 0 且位于 0x0 ~ 0x7FFF FFFF FFFF FFFF ( xuseg or xsseg) 1c: 3c1b8110 lui k1,0x8110 20: 277b4e00 addiu k1,k1,19968 # k1 = 0xFFFF FFFF 8110 4E00, tlb_do_page_fault_0 24: 03600008 jr k1 # call tlb_do_page_fault_0 28: 00000000 nop ... 0xFFFF FFFF 8000 1080: 8c: 403a4000 dmfc0 k0,c0_badvaddr # 取转换失败的虚拟地址 VA,TLB Refill 异常下,cp0_badvaddr (MIPS64 下,64 位长)存放翻译失败的虚拟地址 90: 001ada3e dsrl32 k1,k0,0x8 # VA 逻辑右移 (32+8)位,置入 k1 94: 1760ffdd bnez k1,c <0xc> # k1 不为 0 则跳转到 0xc 处。即 badvaddr[63:41] 不为 0,即高于 40 位不为 0,就跳转。 </source> 对于这种高于 40 位还有有效位的虚拟地址,要不他位于 xkseg,要不就是 xuseg/xsseg 的地址。对于 xkseg 的地址,有有效的地址这是确定的(MIPS32 兼容地址空间内核是用的,高于 40 位的部分都不为 0)。 MIPS64 R2 下,segbit 默认为 40,但具体实现时,MIPS Core 的 segbit 可以超过 40,即实现的真正有效的虚拟地址可以超过 40 位 对只实现 40 位 segbit,出现位于 xuseg/xsseg 的地址且高于 40 位不为 0 的话,这个地址肯定就是用户瞎访问,其就不是一个有效地址,进 do_page_fault() 然后 segment fault 即可 但对实现超过 40 位的 segbit,像 Cavium Octeon CN38xx 实现 49 位,则需要特别对待,因为 4KB 的页大小下,Linux/MIPS 设计时规定整个虚拟地址的有效位是低 40 位,高于 40 的虚拟地址空间,内核没有为之建立页表,即没有映射!虽然你实现了 49 位虚拟地址,但我 Linux 还是用不了,最终也要进入 do_page_fault() 进行处理 <source lang=c> # badvaddr 高于 40 位为 0 98: 403b2000 dmfc0 k1,c0_context # 取 cp0_context 值入 k1 9c: 7c1bb007 dins k1,zero,0x0,0x17 # zero[22:0] 替换 k1[22:0],k1 低 23 位置 0; # 留意:cp0_context[22:4] 是硬件拷贝自 cp0_badvaddr[31:13] # cp0_context[63:23] 是 PGD 的物理地址入口 a0: 377b0540 ori k1,k1,0x540 # 0x540 置入 k1 的低位 a4: 003bdafa dror k1,k1,0xb # drotr k1, k1, 11,即 k1 的低 11 位,和 k1 的高 53 位交换;k1 = k1[10:0] | k1[63:11] 上两条指令实际上是让 0b101 0100 0000 (0x540)或上 k1 的高 11 位,相当于 0xA800 0000 0000 0000 + Phy_PGD_ADDR,其是 PGD 的基地址 @@@ k1 中是 PGD 的基地址 a8: 001ad6fa dsrl k0,k0,0x1b # k0 右移 27 位 ac: 335a1ff8 andi k0,k0,0x1ff8 # 再保留 k0[12:3],实际保留的是 cp0_badvaddr[39:30] 正好是 PGD 的索引值,此时 k0 = cp0_badvaddr[39:30] << 3,64 位下 PGD 的项大小为 2^3 = 8 字节,因此其恰是索引 PGD 的指针 b0: 037ad82d daddu k1,k1,k0 # 获取 VA 对应的 PGD 项,即 VA 对应 PMD 基址的指针 b4: 403a4000 dmfc0 k0,c0_badvaddr # 取转换失败的虚拟地址 VA b8: df7b0000 ld k1,0(k1) # 取对应 PMD 基址 bc: 001ad4ba dsrl k0,k0,0x12 # 右移 18 位 c0: 335a0ff8 andi k0,k0,0xff8 # 保留 k0[12:3],实际是 cp0_badvaddr[29:21] << 3,正好是 PMD 的索引指针,PMD 的项大小也是 2^3 = 8 字节 c4: 037ad82d daddu k1,k1,k0 # 索引 PMD,指向 VA 对应之 PT 基址 c8: 403aa000 dmfc0 k0,c0_xcontext # 取 cp0_xcontext 值,Cavium Octeon 上,BadVPN2 是 xcontext[39:4],置 badvaddr[48:13] cc: df7b0000 ld k1,0(k1) # 取 PT 基址 d0: 335a0ff0 andi k0,k0,0xff0 # 保留 xcontext 的位 11:4,实际为 cp0_badvaddr[20:13] << (3 + 1),这实际为 PT 的索引值,因为 TLB Refill 总是一起填相邻的两项,因此这索引,实际是 VA 对应的偶数项索引 d4: 037ad82d daddu k1,k1,k0 # 索引 PT,指向 VA 对应之偶数项 PTE d8: df7a0000 ld k0,0(k1) # 取 VA 对应偶数项的 PTE dc: df7b0008 ld k1,8(k1) # 取 VA 对应奇数项的 PTE e0: 001ad13a dsrl k0,k0,0x4 # 忽略 PTE 的低 4 位,那是 OS 用的,TLB 的项里没有这些位 e4: 001bd93a dsrl k1,k1,0x4 e8: 003ad0ba dror k0,k0,0x2 # drotr k0, k0, 0x2,k0 低两位和高 62 位交换,这个操作和上面的右移 4 位应该是合起来的忽略 PTE 的低 6 位,因为在 MIPS64 R2 上,写入数据到 entrylo0/1[63:62] 是始终为 0 的。 ec: 40ba1000 dmtc0 k0,c0_entrylo0 # 偶数项 PTE 入 entrylo0 f0: 003bd8ba dror k1,k1,0x2 # 同上面偶数项的分析 f4: 40bb1800 dmtc0 k1,c0_entrylo1 # 奇数项PTE 入 entrylo1 f8: 42000006 tlbwr # 把 entrylo0, entrylo1 随机写入 TLB 的一项 fc: dc1a8078 ld k0,-32648(zero) 100: 42000018 eret ... </source> <br><br> ===== RMI XLR732 ===== <source lang=c> @0xFFFF FFFF 8000 0000 c: 07610005 bgez k1,24 <0x24> /* if vaddr[61] != 1 (0xc000 0000 0000 0000 ~ 0xDFFF FFFF FFFF FFFF), branch */ 10: 3c1bc000 lui k1,0xc000 -----> at delay slot, commit new value to k1 after reading k1 (bgez) # vaddr is at 0xE000000000000000 ~ 0xFFFF FFFF FFFF FFFF,这个地址段里被 64 bit Linux 内核使用且又要经过 TLB 的就是 kseg2 了,内核的模块默认使用这个地址 14: 035bd02f dsubu k0,k0,k1 /* k0 = vaddr - 0xFFFF FFFF C0000000 */ 18: 3c1b8396 lui k1,0x8396 1c: 10000023 b ac <0xac> ----> @@@ 20: 277ba000 addiu k1,k1,-24576 /* 0xFFFF FFFF 8395A000 = module_pg_dir , using the module_pg_dir as the PGD entry */ # vaddr is at 0xC000000000000000 ~ 0xDFFF FFFF FFFF FFFF 24: 001bd83c dsll32 k1,k1,0x0 /* (0xFFFF FFFF C0000000 << 32) */ 28: 035bd02f dsubu k0,k0,k1 /* k0 = vaddr - 0xC000000000000000 */ 2c: 1000001f b ac <0xac> ----> @@@ 30: 3c1b8396 lui k1,0x8396 /* 0xFFFF FFFF 83960000 = swapper_pg_current, using the swapper_pg_current as the PGD entry */ 34: 00000000 38: 00000000 <repeat> ...... @0xFFFF FFFF 8000 0080 (xTLB Refill 入口) 8c: 403a4000 dmfc0 k0,c0_badvaddr 90: 0740001a bltz k0,fc <0xfc> /* if badvaddr >= 0x80000000 00000000 branch */ # vaddr is < 0x8000 0000 0000 000, so it's at kuseg 94: 403b2000 dmfc0 k1,c0_context 98: 001bddfa dsrl k1,k1,0x17 /* get (smp_processor_id() << 3) (26-23), see asm/mmu_context.h */ 9c: 3c1a8396 lui k0,0x8396 /* swapper_pg_current = 0xFFFF FFFF 83960000 */ a0: 037ad82d daddu k1,k1,k0 /* k1 = swapper_pg_current[smp_processor_id()] */ a4: 403a4000 dmfc0 k0,c0_badvaddr a8: df7b2000 ld k1,8192(k1) /* * pgd = *((void *)(k1 + 8192), 8 bytes per pgd entry, pgd_current = 0xFFFF FFFF 8396 2000, * 0x2000 = 8192 * actually it's pgd = pgd_current[smp_processor_id()] */ @@@ ac: 001ad6fa dsrl k0,k0,0x1b # >> 27 b0: 335a1ff8 andi k0,k0,0x1ff8 /* get (vaddr[39:30] << 3), for indexing pgd */ b4: 037ad82d daddu k1,k1,k0 /* index pgd */ b8: 403a4000 dmfc0 k0,c0_badvaddr bc: df7b0000 ld k1,0(k1) /* get p_pmd */ c0: 001ad4ba dsrl k0,k0,0x12 c4: 335a0ff8 andi k0,k0,0xff8 /* get (vaddr[29:21] << 3), for indexing pmd */ c8: 037ad82d daddu k1,k1,k0 /* index pmd */ cc: 403aa000 dmfc0 k0,c0_xcontext d0: df7b0000 ld k1,0(k1) /* get p_pt */ d4: 335a0ff0 andi k0,k0,0xff0 /* get (va[20:13] << 4), actually use va[20:12] index the pt, va[12]=0, for indexing pt */ d8: 037ad82d daddu k1,k1,k0 /* index pt */ dc: df7a0000 ld k0,0(k1) /* get even page addr */ <------------- e0: df7b0008 ld k1,8(k1) /* get odd page addr */ e4: 001ad1ba dsrl k0,k0,0x6 /* ignore the low 6 bits, it's for os */ e8: 409a1000 mtc0 k0,c0_entrylo0 /* tlb even page entry */ ec: 001bd9ba dsrl k1,k1,0x6 /* same as above */ f0: 409b1800 mtc0 k1,c0_entrylo1 /* tlb odd page entry */ f4: 42000006 tlbwr /* random write tlb */ f8: 42000018 eret # go here, vaddr is >=0x8000 0000 0000 0000, so it's in xkphys or xkseg; and 0x8000 0000 0000 0000 ~ 0xBFFF FFFF FFFF FFFF is xkphys, unmapped, do not index TLB, so vaddr is at 0xc000 0000 0000 0000 fc: 001ad8b8 dsll k1,k0,0x2 # vaddr << 2, will be test the vaddr[61] 100: 1000ffc2 b c <0xc> 104: 00000000 nop 108: 00000000 nop </source> <br><br> ==== EXL=1 时 TLB Refill 的处理 ==== EXL=1 时,TLB Refill 进的是其他例外入口下 TLB Invalid 异常处理函数,TLB Invalid 分两类异常,一个是读数据引发的 (ExcCode=2, 处理函数 handle_tlbl),一个是写数据引发的 (ExcCode=3, 处理函数是 handle_tlbs)。 位于其他例外入口处的处理函数 except_vec3_generic 只是根据 ExcCode 跳转到具体例外的处理函数,TLB Load Invalid 进入 handle_tlbl;TLB Write Invalid 进入 handle_tlbs。这两个函数也是动态生成。 <br><br> ===== Cavium Octeon handle_tlbl 分析 ===== handle_tlbl (位于代码段,不受向量空间限制。从其它例外入口进入): <source lang=c> c: 403a4000 dmfc0 k0,c0_badvaddr 10: 001ada3e dsrl32 k1,k0,0x8 # 失败地址右移 40 位 14: 17600032 bnez k1,e0 <0xe0> # 40 位外不为 0 则跳转。不管CPU 具体实现了多少位的虚拟地址,对内核来讲 40 位外不为 0 都是异常地址,需要小心处理 18: 403b2000 dmfc0 k1,c0_context 1c: 7c1bb007 dins k1,zero,0x0,0x17 20: 377b0540 ori k1,k1,0x540 24: 003bdafa dror k1,k1,0xb # 以上四条指令是为:k1 = 0xA800 0000 0000 0000 + PGD_Phy,即使用一个固定映射的虚址访问 PGD 28: 001ad6fa dsrl k0,k0,0x1b # k0 里放的是 c0_badvaddr 2c: 335a1ff8 andi k0,k0,0x1ff8 # PGD offset 30: 037ad82d daddu k1,k1,k0 # PMD base addr 34: 403a4000 dmfc0 k0,c0_badvaddr 38: df7b0000 ld k1,0(k1) 3c: 001ad4ba dsrl k0,k0,0x12 40: 335a0ff8 andi k0,k0,0xff8 # PMD offset 44: 037ad82d daddu k1,k1,k0 # get PT base addr 48: 403a4000 dmfc0 k0,c0_badvaddr 4c: df7b0000 ld k1,0(k1) 50: 001ad27a dsrl k0,k0,0x9 54: 335a0ff8 andi k0,k0,0xff8 # PT offset 58: 037ad82d daddu k1,k1,k0 # index PT 5c: d37a0000 lld k0,0(k1) # get PTE 60: 42000008 tlbp # 查找失效地址 VA1 对应之 TLB 项,有则将 index 写入 cp0_index,无则将 cp0_index[31] = 1;注意所有 TLB 相关的异常 (Refill, Invalid, Modified) 出现时,CPU 都会把失效地址的高位置入 EntryHi[VPN2] 64: 335a0001 andi k0,k0,0x1 68: 13400020 beqz k0,ec <0xec> # 判断 PTE 的最低位 _PAGE_PRESENT,为 0 表示该页未分配或未在内存,则跳转到 do_page_fault() 处理。此应是 EXL=1 TLB Miss 的在此的主要路径,进入这里的 TLB Miss,多数情形是页面尚未分配,在 TLB Miss 的处理中,索引 PT 失败。经此一路,在 do_page_fault() 中,系统会为其分配页面,填充页表项,往下就不会是页面尚未分配的情形 注意,tlbp 失败只能是失效地址对应页表没有分配或不在内存(交换出去);往下 tlbp 不可能是其失败的情形,如果失效地址有页表项(有效无效皆可),其也不会在 TLB Miss 中再次 TLB Miss 另外 tlbp 失败会将 cp0_index[31] = 1,这个值远远大于现代 MIPS 实现的 TLB 项数目,随后来一条 tlbr 的话,其结果未定义! 6c: d37a0000 lld k0,0(k1) # 该页已分配,get PTE again 70: 335a0080 andi k0,k0,0x80 74: 13400008 beqz k0,98 <0x98> # 判断 PTE 的 _PAGE_VALID 位,为 0 表示该页无效,跳转。失效地址对应的页表项无效,其就不是 EXL=1 TLB Miss 引起的了,应为 TLB Invalid 异常 78: 00000000 nop # 页表项有效 7c: 42000001 tlbr # 读 TLB,cp0_Index 对应 TLB 项存入 EntryHi, EntryLo0/1, PageMask 80: 337a0008 andi k0,k1,0x8 # k1 为页表项指针,此为判断奇偶页操作 84: 13400002 beqz k0,90 <0x90> # 奇数项则跳转 88: 403a1000 dmfc0 k0,c0_entrylo0 # 延迟槽,总是被执行。取奇数页在 TLB 中的值 8c: 403a1800 dmfc0 k0,c0_entrylo1 # 偶数项 90: 335a0002 andi k0,k0,0x2 # TLB 项之 V 位 94: 17400015 bnez k0,ec <0xec> # 不为 0(有效)则跳转到 do_page_fault() 处理。这是一个奇怪的现象,能找到失效地址对应的TLB项(非 EXL=1 TLB Refill),页表项目有效(非空 PTE),且对应的 TLB 项亦有效(非 TLB Invalid),应由 do_page_fault() 进一步甄别 # 失效地址对应之页表项无效 (V=0);或者对应页表项有效,却 TLB 项无效 (V=0) 98: d37a0000 lld k0,0(k1) # 再取 PTE 9c: 375a0084 ori k0,k0,0x84 # 置 _PAGE_VALID 和 _PAGE_WRITE a0: f37a0000 scd k0,0(k1) # 更新页表项 a4: 1340ffed beqz k0,5c <0x5c> # k0=0 则事务操作失败,重做 a8: 00000000 nop ac: 377b0008 ori k1,k1,0x8 b0: 3b7b0008 xori k1,k1,0x8 # 重置页表项指针,使其重新指向失效地址对应之奇数项 b4: df7a0000 ld k0,0(k1) # 以下操作取失效地址的页表项,填充 TLB,分析同 TLB Refill b8: df7b0008 ld k1,8(k1) bc: 001ad13a dsrl k0,k0,0x4 c0: 001bd93a dsrl k1,k1,0x4 c4: 003ad0ba dror k0,k0,0x2 c8: 40ba1000 dmtc0 k0,c0_entrylo0 cc: 003bd8ba dror k1,k1,0x2 d0: 40bb1800 dmtc0 k1,c0_entrylo1 d4: 42000002 tlbwi # 写入 Index 指定的 TLB 入口中 d8: dc1a8078 ld k0,-32648(zero) dc: 42000018 eret # 40位外有货,地址异常 ,需要小心对待 e0: 3c1b817c lui k1,0x817c e4: 1000ffd0 b 28 <0x28> # k1 = 0xFFFF FFFF 817B E000, it's swapper_pg_dir e8: 277be000 addiu k1,k1,-8192 ec: 08441380 j 1104e00 <0x1104e00> # jump to tlb_do_page_fault_0, call do_page_fault(not write) f0: 00000000 nop ... </source> <br><br> ===== Cavium Octeon handle_tlbs 分析 ===== handle_tlbs (位于代码段,不受向量空间限制): <source lang=c> c: 403a4000 dmfc0 k0,c0_badvaddr 10: 001ada3e dsrl32 k1,k0,0x8 14: 17600028 bnez k1,b8 <0xb8> 18: 403b2000 dmfc0 k1,c0_context 1c: 7c1bb007 dins k1,zero,0x0,0x17 20: 377b0540 ori k1,k1,0x540 24: 003bdafa dror k1,k1,0xb 28: 001ad6fa dsrl k0,k0,0x1b 2c: 335a1ff8 andi k0,k0,0x1ff8 30: 037ad82d daddu k1,k1,k0 34: 403a4000 dmfc0 k0,c0_badvaddr 38: df7b0000 ld k1,0(k1) 3c: 001ad4ba dsrl k0,k0,0x12 40: 335a0ff8 andi k0,k0,0xff8 44: 037ad82d daddu k1,k1,k0 48: 403a4000 dmfc0 k0,c0_badvaddr 4c: df7b0000 ld k1,0(k1) 50: 001ad27a dsrl k0,k0,0x9 54: 335a0ff8 andi k0,k0,0xff8 58: 037ad82d daddu k1,k1,k0 5c: d37a0000 lld k0,0(k1) 60: 42000008 tlbp 64: 335a0003 andi k0,k0,0x3 68: 3b5a0003 xori k0,k0,0x3 6c: 17400015 bnez k0,c4 <0xc4> # 失效地址对应之页表项无效 (V=0);或者对应页表项有效,却 TLB 项无效 (V=0) 70: d37a0000 lld k0,0(k1) 74: 375a018c ori k0,k0,0x18c # 置 _PAGE_VALID, _PAGE_DIRTY, _PAGE_WRITE, _PAGE_ACCESSED 78: f37a0000 scd k0,0(k1) 7c: 1340fff7 beqz k0,5c <0x5c> # 事务失败,重做 80: 00000000 nop 84: 377b0008 ori k1,k1,0x8 88: 3b7b0008 xori k1,k1,0x8 8c: df7a0000 ld k0,0(k1) 90: df7b0008 ld k1,8(k1) 94: 001ad13a dsrl k0,k0,0x4 98: 001bd93a dsrl k1,k1,0x4 9c: 003ad0ba dror k0,k0,0x2 a0: 40ba1000 dmtc0 k0,c0_entrylo0 a4: 003bd8ba dror k1,k1,0x2 a8: 40bb1800 dmtc0 k1,c0_entrylo1 ac: 42000002 tlbwi b0: dc1a8078 ld k0,-32648(zero) b4: 42000018 eret b8: 3c1b817c lui k1,0x817c bc: 1000ffda b 28 <0x28> # k1 = 0xFFFF FFFF 817B E000, it's swapper_pg_dir c0: 277be000 addiu k1,k1,-8192 c4: 084413c8 j 1104f20 <0x1104f20> # jump to tlb_do_page_fault_1, call do_page_fault(write) c8: 00000000 nop cc: 00000000 nop </source> 除标出的外,其它与 handle_tlbl 同 <br><br> ===== RMI XLR732 handle_tlbl 分析 ===== <source lang=c> handle_tlbl: c: 403a4000 dmfc0 k0,c0_badvaddr <---- handle_tlbl start 10: 07400027 bltz k0,b0 <0xb0> # if badvaddr >= 0x80000000 00000000 branch 14: 403b2000 dmfc0 k1,c0_context 18: 001bddfa dsrl k1,k1,0x17 /* get (smp_processor_id() << 3) (26-23) */ 1c: 3c1a8396 lui k0,0x8396 /* 0xffffffff83960000 + 0x2000 = pgd_current */ 20: 037ad82d daddu k1,k1,k0 24: 403a4000 dmfc0 k0,c0_badvaddr 28: df7b2000 ld k1,8192(k1) /* pgd = pgd_current[smp_processor_id()] */ @@ 2c: 001ad6fa dsrl k0,k0,0x1b # >> 27 30: 335a1ff8 andi k0,k0,0x1ff8 /* get (vaddr[39:30] << 3), for indexing pgd */ 34: 037ad82d daddu k1,k1,k0 /* index pgd */ 38: 403a4000 dmfc0 k0,c0_badvaddr 3c: df7b0000 ld k1,0(k1) /* get pmd */ 40: 001ad4ba dsrl k0,k0,0x12 # >> 18 44: 335a0ff8 andi k0,k0,0xff8 # get (vaddr[29:21] << 3) 48: 037ad82d daddu k1,k1,k0 # index pmd 4c: 403a4000 dmfc0 k0,c0_badvaddr 50: df7b0000 ld k1,0(k1) # get p_pt 54: 001ad27a dsrl k0,k0,0x9 58: 335a0ff8 andi k0,k0,0xff8 # get (vaddr[20:12]) 5c: 037ad82d daddu k1,k1,k0 # index pt 60: d37a0000 lld k0,0(k1) # get pt entry 64: 42000008 tlbp # to distinguish TLB invalid or TLB refill exception 68: 335a0003 andi k0,k0,0x3 6c: 3b5a0003 xori k0,k0,0x3 # _PRESENT and _READ 70: 1740001a bnez k0,dc <0xdc> # low two bits is not 011, branch @@-->out 74: d37a0000 lld k0,0(k1) # get pt entry again 78: 375a0088 ori k0,k0,0x88 7c: f37a0000 scd k0,0(k1) # set pt entry's pte[7] = 1, pte[3] =1 80: 1340fff7 beqz k0,60 <0x60> 84: 00000000 nop 88: 377b0008 ori k1,k1,0x8 8c: 3b7b0008 xori k1,k1,0x8 # for getting even page 90: df7a0000 ld k0,0(k1) # get even page addr 94: df7b0008 ld k1,8(k1) # get odd page addr 98: 001ad1ba dsrl k0,k0,0x6 9c: 409a1000 mtc0 k0,c0_entrylo0 a0: 001bd9ba dsrl k1,k1,0x6 a4: 409b1800 mtc0 k1,c0_entrylo1 a8: 42000002 tlbwi ac: 42000018 eret @@ b0: 001ad8b8 dsll k1,k0,0x2 # go here, vaddr is in xkphys or xkseg (>0x8000..0000) # 0x8000..0000 ~ 0xc000..0000 is xkphys, unmapped, # so vaddr > 0xc000..0000; vaddr << 2 b4: 07610005 bgez k1,cc <0xcc> # vaddr[61] != 1 (0xc000..0000 ~ 0xdfff..ffff), branch b8: 3c1bc000 lui k1,0xc000 # vaddr, 0xe000..0000 ~ 0xffff..ffff bc: 035bd02f dsubu k0,k0,k1 # k0 = vaddr - 0xffffffffc0000000 c0: 3c1b8396 lui k1,0x8396 c4: 1000ffd9 b 2c <0x2c> c8: 277ba000 addiu k1,k1,-24576 # k1 = 0xffffffff8395a000, module_pg_dir # module_pg_dir & swapper_pg_dir don't need the smp @@ # vaddr, 0xc000..0000 ~ 0xdfff..ffff cc: 001bd83c dsll32 k1,k1,0x0 # 0xc0000000 00000000 d0: 035bd02f dsubu k0,k0,k1 # k0 = vaddr - 0xc0000000 00000000 d4: 1000ffd5 b 2c <0x2c> d8: 3c1b8396 lui k1,0x8396 # only swapper_pg_current @@-->do_page_fault dc: 08d01150 j 3404540 <0x3404540> e0: 00000000 nop ... </source> <br><br> === 其它例外入口初始化 === 其它例外的入口初始化位于: <source lang=c> [arch/mips/kernel/traps.c] void __init trap_init() { ...... /* * Copy the generic exception handlers to their final destination. * This will be overriden later as suitable for a particular * configuration. */ set_handler(0x180, &except_vec3_generic, 0x80); ....... } </source> set_handler 同样定义该文件中: <source lang=c> /* Install CPU exception handler */ void __init set_handler(unsigned long offset, void *addr, unsigned long size) { memcpy((void *)(ebase + offset), addr, size); local_flush_icache_range(ebase + offset, ebase + offset + size); } </source> 其主要的操作就是把其它例外处理函数 except_vec3_generic,复制到对应的入口处,这个入口一般为 ebase + 0x180。主要的不同在于 ebase 的值,这个在 cavium 上为 CKSEG0 + read_c0_ebase(),loongson2 上为 CKSEG0 = 0xFFFF FFFF8000 0180: <source lang=c> void __init trap_init() { ...... if (cpu_has_veic || cpu_has_vint) { unsigned long size = 0x200 + VECTORSPACING*64; ebase = (unsigned long) __alloc_bootmem(size, 1 << fls(size), 0); } else { ebase = CKSEG0; if (cpu_has_mips_r2) ebase += (read_c0_ebase() & 0x3ffff000); } ...... } </source> 其它例外处理函数 except_vec3_generic 定义于: <source lang=c> [arch/mips/kernel/genex.S] /* * General exception vector for all other CPUs. * * Be careful when changing this, it has to be at most 128 bytes * to fit into space reserved for the exception handler. */ NESTED(except_vec3_generic, 0, sp) .set push .set noat #if R5432_CP0_INTERRUPT_WAR mfc0 k0, CP0_INDEX #endif mfc0 k1, CP0_CAUSE andi k1, k1, 0x7c # K1 = ExcCode * 4,32bit #ifdef CONFIG_64BIT dsll k1, k1, 1 # k1 = ExcCode * 8,64bit 指针为 8 字节 #endif PTR_L k0, exception_handlers(k1) jr k0 .set pop END(except_vec3_generic) </source> 代码很短,其功能为:取 cp0_cause 之 ExcCode 值,然后跳转到 exception_handlers[ExcCode] 处。这个 exception_handler 实际是一张表,每一项放的是具体异常处理函数的指针。ExcCode 为 cp0_cause[6:2],cp0_cause & 0x7c 就是 ExcCode * 4;64位下,指针长度为 8 字节,因此其还要左移一位,得 ExcCode * 8 这个 exception_handlers 定义于: <pre> [arch/mips/kernel/traps] unsigned long exception_handlers[32]; </pre> 在 trap_init() 中填冲: <source lang=c> /* * Setup default vectors */ for (i = 0; i <= 31; i++) set_except_vector(i, handle_reserved); ...... set_except_vector(0, rollback ? rollback_handle_int : handle_int); set_except_vector(1, handle_tlbm); set_except_vector(2, handle_tlbl); set_except_vector(3, handle_tlbs); set_except_vector(4, handle_adel); set_except_vector(5, handle_ades); set_except_vector(6, handle_ibe); set_except_vector(7, handle_dbe); set_except_vector(8, handle_sys); set_except_vector(9, handle_bp); set_except_vector(10, rdhwr_noopt ? handle_ri : (cpu_has_vtag_icache ? handle_ri_rdhwr_vivt : handle_ri_rdhwr)); set_except_vector(11, handle_cpu); set_except_vector(12, handle_ov); set_except_vector(13, handle_tr); ...... </source> 这个 set_except_vector() 定义于同一文件中: <source lang=c> void __init *set_except_vector(int n, void *addr) { unsigned long handler = (unsigned long) addr; unsigned long old_handler = exception_handlers[n]; exception_handlers[n] = handler; if (n == 0 && cpu_has_divec) { // 处理扩展中断向量的情形, MIPS32/64 R2 都带的一个可选 feature,loongson2 和 cavium 都没有实现,因此我们不关心 unsigned long jump_mask = ~((1 << 28) - 1); u32 *buf = (u32 *)(ebase + 0x200); unsigned int k0 = 26; if ((handler & jump_mask) == ((ebase + 0x200) & jump_mask)) { uasm_i_j(&buf, handler & ~jump_mask); uasm_i_nop(&buf); } else { UASM_i_LA(&buf, k0, handler); uasm_i_jr(&buf, k0); uasm_i_nop(&buf); } local_flush_icache_range(ebase + 0x200, (unsigned long)buf); } return (void *)old_handler; } </source> 其完成的主要操作就是将传来的具体例外处理函数地址赋值给 exception_handlers 的元素 n 可以看到用来索引的 ExcCode 是这样与具体例外处理函数相关的,MIPS 规定 ExcCode 值表示的含义如下: <pre> 0 Int 中断 1 Mod TLB 修改异常 2 TLBL TLB 读异常 3 TLBS TLB 写异常 4 AdEL 读地址错误异常 5 AdEs 写地址错误异常 6 IBE 总线错误异常(取指令) 7 DBE 总线错误异常(读写数据) 8 Sys 系统调用异常 ...... </pre> 上述的 handle_int, handle_sys, handle_adel, handle_ades, handle_ibe, handle_dbe ... 由下面的宏生成: <source lang=c> [arch/mips/kernel/genex.S] BUILD_HANDLER adel ade ade silent /* #4 */ BUILD_HANDLER ades ade ade silent /* #5 */ BUILD_HANDLER ibe be cli silent /* #6 */ BUILD_HANDLER dbe be cli silent /* #7 */ BUILD_HANDLER bp bp sti silent /* #9 */ BUILD_HANDLER ri ri sti silent /* #10 */ BUILD_HANDLER cpu cpu sti silent /* #11 */ BUILD_HANDLER ov ov sti silent /* #12 */ BUILD_HANDLER tr tr sti silent /* #13 */ BUILD_HANDLER fpe fpe fpe silent /* #15 */ BUILD_HANDLER mdmx mdmx sti silent /* #22 */ #ifdef CONFIG_HARDWARE_WATCHPOINTS /* * For watch, interrupts will be enabled after the watch * registers are read. */ BUILD_HANDLER watch watch cli silent /* #23 */ #else BUILD_HANDLER watch watch sti verbose /* #23 */ #endif BUILD_HANDLER mcheck mcheck cli verbose /* #24 */ BUILD_HANDLER mt mt sti silent /* #25 */ BUILD_HANDLER dsp dsp sti silent /* #26 */ BUILD_HANDLER reserved reserved sti verbose /* others */ BUILD_HANDLER 定义为: .macro BUILD_HANDLER exception handler clear verbose __BUILD_HANDLER \exception \handler \clear \verbose _int .endm .macro __BUILD_HANDLER exception handler clear verbose ext .align 5 NESTED(handle_\exception, PT_SIZE, sp) .set noat SAVE_ALL FEXPORT(handle_\exception\ext) __BUILD_clear_\clear .set at __BUILD_\verbose \exception move a0, sp PTR_LA ra, ret_from_exception j do_\handler END(handle_\exception) .endm </source> <br><br> <br><br>
返回到
64位多核 MIPS 异常和中断内核代码分析 (1)
。
个人工具
登录
名字空间
页面
讨论
变换
查看
阅读
查看源代码
查看历史
操作
搜索
导航
首页
社区专页
新闻动态
最近更改
随机页面
帮助
工具箱
链入页面
相关更改
特殊页面