64位多核 MIPS 异常和中断内核代码分析 (1)

来自Jack's Lab
(版本间的差异)
跳转到: 导航, 搜索
(Cavium Octeon CN38xx)
(Cavium Octeon handle_tlbs 分析)
第567行: 第567行:
 
</pre>
 
</pre>
 
   
 
   
除标出的外,其它与 handle_tlbl 同
+
除标出的外,其它与 handle_tlbl 同
  
  
 
<br><br>
 
<br><br>
 
<br><br>
 
<br><br>

2013年7月17日 (三) 15:35的版本

目录

1 高优先级例外入口初始化

本文不涉及 Bootloader,因此 Reset 例外入口处的 Bootloader 我们就不讨论了。

多核环境下,所有核默认指向的例外入口基地址都是一样的,尽管可以修改各自核内部的 EBase 寄存器来改变例外入口基地址,但我们此处只讨论所有核运行在对称多处理模式(SMP) 下的情形,即所有核都使用同样的例外入口



1.1 Cache Error

先看看 Cache Error 时,Linux Kernel 都做了什么



1.1.1 Loongson 2E Cache Error

Cache Error 例外入口初始化,位于:

[arch/mips/mm/c-r4k.c]
void __init r4k_cache_init()
{   
    ......
    set_uncached_handler(0x100, &except_vec2_generic, 0x80);
    ......
}


因为 cache 错误时可以 cache 的 KSEG0 段不能用了,则 cache 错误例外处理函数位于 KSEG1 之0xFFFF FFFF A000 0000 + 0x100 处,长度最大为128 Bytes(32 条指令),处理函数为 except_vec2_generic,定义于:

[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)


发生 Cache Error 后,Cache 已不再可信,因此原 KSEG0 不能再使用 Cache,首先改变其为 Uncached,然后进入真正的处理函数 cache_parity_error:

[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!");
}


事实上,当出现 Cache Error 时,系统已经没法修复,只能打出一些可供判断的信息后 panic :(

另外 set_uncached_handler 定义于:

[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);
}

其中 CKSEG1ADDR 的宏用于将虚址 ebase 转化为对应的 uncached 段的虚址 0xFFFF FFFF A000 0000。可以看到这个函数负责把例外处理函数 except_vec2_generic 复制到 Cache Error 例外的入口 0xFFFF FFFFF A000 0100 处,即物理地址 0x100 处


1.1.2 Cavium Octeon Cache Error

Cavium Octeon 的 Cache 错误处理和 2E 大同小异,只是处理过程多了些鲁棒性

其Cache Error 例外入口初始化,位于:

[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);
    ......
}


将例外处理函数 except_vec2_octeon 复制到 ebase + 0x100 处,在 Cavium 上 ebase 为:

 
[arch/mips/cavium-octeon/setup.c]
uint32_t ebase = read_c0_ebase() & 0x3ffff000

实际就是 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 定义于:

[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)


回顾一下,例外发生时 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

 /* 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)


[arch/mips/mm/c-octeon.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");
}



1.2 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>


1.2.1 Cavium Octeon CN38xx

CN38xx实现 49 bit 虚拟地址,其 segbit 为 49


Mips64-mem-map.png


其中内核空间是 xkphys 和 xkseg;其中 xkphys 是固定映射,不经 TLB (unmapped)

用户空间是 xuseg



1.2.2 EXL=0 时 TLB Refill 的处理

这个的处理函数,在 Cavium Octeon 上位于 0xFFFF FFFF 8000 1080 处,但往往这个处理函数都会超过 0x80 字节,所以动态生成过程就使用原来 32 位的入口空间再放一部分代码,在 64 位下,这个 32 位的 TLB Refill 入口空间是不用的:


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,就跳转。
 
对于这种高于 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() 进行处理
 
 
# 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
        ...



1.2.3 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。这两个函数也是动态生成。



1.2.3.1 Cavium Octeon handle_tlbl 分析

handle_tlbl (位于代码段,不受向量空间限制。从其它例外入口进入):

   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
        ...



1.2.3.2 Cavium Octeon handle_tlbs 分析

handle_tlbs (位于代码段,不受向量空间限制):

    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

除标出的外,其它与 handle_tlbl 同






个人工具
名字空间

变换
操作
导航
工具箱