天天看點

RT-Smart riscv64彙編注釋RT-Smart riscv64彙編注釋

RT-Smart riscv64彙編注釋

以rt-smart在全志D1上的代碼為例,主要注釋了rt-smart在riscv64上的系統初始化和異常處理的代碼

倉庫位址https://gitee.com/rtthread/rt-thread/tree/rt-smart

啟動

代碼路徑

libcpu\risc-v\t-head\c906\startup_gcc.S
/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018/10/01     Bernard      The first version
 * 2018/12/27     Jesven       Add SMP support
 * 2020/6/12      Xim          Port to QEMU and remove SMP support
 */

#define __ASSEMBLY__
#define SSTATUS_FS      0x00006000U /* initial state of FPU, clear to disable */
#include <cpuport.h>

  .global	_start
  .section ".start", "ax"
_start:
  j 1f
  .word 0xdeadbeef
  .align 3
  .global g_wake_up
  g_wake_up:
      .dword 1
      .dword 0
1:
  csrw sie, 0  /*超級使用者模式中斷使能關閉*/
  csrw sip, 0  /*超級使用者模式中斷等待關閉*/
  la t0, trap_entry /*将trap_entry的位址放入t0寄存器*/
  csrw stvec, t0 /*配置異常服務程式的入口位址*/

  li x1, 0
  /*...........*/       /*初始化通用寄存器*/
  li x31,0

  /* set to disable FPU */
  li t0, SSTATUS_FS /*将FS的bit位寫入t0寄存器*/
  csrc sstatus, t0  /*清除sstatus中的FS bit,關閉浮點單元*/
  li t0, 0x40000  /*當 SUM=1 時,超級使用者模式下,加載、存儲和取指令請求可以通路标記為使用者态的虛拟記憶體空間*/
  csrs sstatus, t0 /*置位sstatus中的SUM位*/

.option push
.option norelax
  la gp, __global_pointer$
.option pop

  // removed SMP support here
  la   sp, __stack_start__  /*棧指針的值來自于連結腳本中的__stack_start*/
  li   t0, __STACKSIZE__ 
  add  sp, sp, t0   /*棧自上到下增長*/
  csrw sscratch, sp  /*sscratch存儲棧頂的位址 */
  j primary_cpu_entry /*跳轉到board中的C程式入口*/
           
//BSP的C入口
void primary_cpu_entry(void)
{
    extern void entry(void);

    //初始化BSS
    init_bss();
    //關中斷
    rt_hw_interrupt_disable();
    rt_assert_set_hook(__rt_assert_handler);
    //啟動RT-Thread Smart核心
    entry();
}
           

異常處理

異常處理上半部分

/*libcpu\risc-v\t-head\c906\interrupt_gcc.S*/

#define __ASSEMBLY__
#include "cpuport.h"
#include "encoding.h"
#include "stackframe.h"

  .section      .text.entry
  .align 2
  .global trap_entry
  .extern __stack_cpu0
  .extern get_current_thread_kernel_stack_top
trap_entry: /*異常處理函數的入口*/
    //backup sp
    csrrw sp, sscratch, sp /*将目前棧與sscratch做交換*/
    //load interrupt stack
    la sp, __stack_cpu0 /*sp指向cpu0的中斷棧的棧頂*/
    //backup context
    SAVE_ALL /*CPU寄存器入棧,使能浮點的情況下浮點相關的寄存器也要入棧 并且要儲存sstatus中浮點的運算狀态*/
    
    RESTORE_SYS_GP /*gp操作不用了解*/

    //check syscall
    csrr t0, scause /*讀取scaue到t0*/
    li t1, 8    //environment call from u-mode /*使用者模式環境調用異常*/
    beq t0, t1, syscall_entry   /*如果是系統調用則跳轉到系統調用處理函數,這個函數最終會調用sret*/

    csrr a0, scause /*讀取scause到a0,機器模式異常事件向量寄存器(MCAUSE)用于儲存觸發異常的異常事件向量号,用于在異常服務程式中處理對應事件*/
    csrrc a1, stval, zero /*讀取stval到a1,發生異常或者中斷,且在機器模式響應時,處理器會更新 pc 到 MEPC,并根據異常類型更新 MTVAL*/
    csrr  a2, sepc /*讀取sepc到a2, 超級使用者模式異常保留程式計數器(SEPC)用于存儲程式從異常服務程式退出時的程式計數器值(即
PC 值)*/
    mv    a3, sp  /*讀取sp的值到a3*/

    /* scause, stval, sepc, sp */
    call  handle_trap  /*進行中斷處理*/
           

中斷處理

/*libcpu\risc-v\t-head\c906\trap.c*/
/* Trap entry */
void handle_trap(rt_size_t scause,rt_size_t stval,rt_size_t sepc,struct rt_hw_stack_frame *sp)
{
    /*
     SCAUSE
     bit63 Interrupt-中斷标記位
     當 Interrupt 位為 0 時,表示觸發異常的來源不是中斷, Exception Code 按照異常解析。當 Interrupt 位為 1 時,表示觸發異常的來源是中斷, Exception Code 按照中斷解析。該位會被 reset 置為 1’ b0。
     bit0~4 Exception Code-異常向量号位 
     在處理器響應異常或中斷時,該域會被更新為對應異常号,具體請參考 表 3.9 異常和中斷向量分
配。該位會被 reset 置為 5’ b0。
     */


    /*我了解這裡是想擷取Exception Code,但是Exception Code是bit0 ~ bit4,這裡用__MASK(5UL)更合适吧*/
    rt_size_t id = __MASKVALUE(scause,__MASK(63UL));
    const char *msg;

    /* supervisor external interrupt */
    /*如果scause的bit63是1,scause的bit0~4是9超級使用者模式外部中斷*/
    if ((SCAUSE_INTERRUPT & scause) && SCAUSE_S_EXTERNAL_INTR == (scause & 0xff))
    {
        rt_interrupt_enter();
        plic_handle_irq();
        rt_interrupt_leave();
        return;
    } /*如果scause的bit63是1,scause的bit0~4是超級使用者模式計時器中斷*/
    else if ((SCAUSE_INTERRUPT | SCAUSE_S_TIMER_INTR) == scause)
    {
        /* supervisor timer */
        rt_interrupt_enter();
        tick_isr();
        rt_interrupt_leave();
        return;
    } /*其他中斷*/
    else if (SCAUSE_INTERRUPT & scause)
    {
        if(id < sizeof(Interrupt_Name) / sizeof(const char *))
        {
            msg = Interrupt_Name[id];
        }
        else
        {
            msg = "Unknown Interrupt";
        }
        LOG_E("Unhandled Interrupt %ld:%s\n",id,msg);
    }
    else /*異常處理*/
    {
#ifdef RT_USING_USERSPACE
        /* page fault 缺頁異常處理*/
        if (id == EP_LOAD_PAGE_FAULT ||
            id == EP_STORE_PAGE_FAULT)
        {
            arch_expand_user_stack((void *)stval);
            return;
        }
#endif  /*其他異常處理,走到這裡後列印一些必要資訊,最終會走到while(1),進入死循環*/
        if(id < sizeof(Exception_Name) / sizeof(const char *))
        {
            msg = Exception_Name[id];
        }
        else
        {
            msg = "Unknown Exception";
        }

        rt_kprintf("Unhandled Exception %ld:%s\n",id,msg);
    }

    rt_kprintf("scause:0x%p,stval:0x%p,sepc:0x%p\n",scause,stval,sepc);
    dump_regs(sp);
    while(1);
}
           

在rt-smart中任務切換有三個相關的線程函數

  • rt_hw_context_switch_to():沒有來源線程,切換到目标線程,在排程器啟動第一個線程的時候 被調用
  • rt_hw_context_switch():線上程環境下,從目前線程切換到目标線程
  • rt_hw_context_switch_interrupt ():在中斷環境下,從目前線程切換到目标線程。

rt_hw_context_switch_interrupt ()會将rt_thread_switch_interrupt_flag置為1,真正的線程切換動作在異常處理函數中完成。

異常處理下半部分

/* need to switch new thread  查詢線程切換的flag是否被置位為1*/
    la    s0, rt_thread_switch_interrupt_flag /*讀取rt_thread_switch_interrupt_flag*/
    lw    s2, 0(s0)
    beqz  s2, spurious_interrupt /*rt_thread_switch_interrupt_flag如果為0那麼直接跳轉到spurious_interrupt進行寄存器恢複,并調用sret回到異常之前的狀态*/
    sw    zero, 0(s0) /*rt_thread_switch_interrupt_flag = 0*/

.global rt_hw_context_switch_interrupt_do
rt_hw_context_switch_interrupt_do:

//swap to thread kernel stack
    csrr t0, sstatus  /*讀取sstatus到t0*/
    andi t0, t0, 0x100 /*bit8 超級使用者模式保留特權狀态位*/
    /*
     該位用于儲存處理器在降級到超級使用者模式進入異常服務程式前的特權狀态。
     • 當 SPP 為 2’ b00 時,表示處理器進入異常服務程式前處于使用者模式;
     • 當 SPP 為 2’ b01 時,表示處理器進入異常服務程式前處于超級使用者模式;
     該位會被 reset 置 2’ b01。
    */
    beqz t0, __restore_sp_from_tcb_interrupt /*如果是核心态發生異常*/

__restore_sp_from_sscratch_interrupt:
    csrr t0, sscratch /*擷取發生異常時的上下文資料*/
    j __move_stack_context_interrupt /*如果是使用者态發生異常*/
    
/*擷取目前線程的棧頂位置存到t0中*/
__restore_sp_from_tcb_interrupt:
    la    s0, rt_interrupt_from_thread
    LOAD  a0, 0(s0)
    jal rt_thread_sp_to_thread
    jal get_thread_kernel_stack_top
    mv t0, a0

__move_stack_context_interrupt:
    mv t1, sp//src /*目前棧,目前棧存儲的是發生異常時的通用寄存器資訊*/
    mv sp, t0//switch stack /* 将發生異常時的棧的值寫回到sp寄存器 */
    addi sp, sp, -CTX_REG_NR * REGBYTES  /*棧指針向下移動CTX_REG_NR * REGBYTES*/
    //copy context
    li s0, CTX_REG_NR//cnt /*需要恢複的寄存器的個數加載到s0*/
    mv t2, sp//dst /*棧指針加載到t2*/
/*總結就是,目前CPU的中斷棧存儲了目前線程的通用寄存器的資訊,如果發生任務切換,需要把這些資訊拷貝到線程的棧裡*/

copy_context_loop_interrupt:
    LOAD t0, 0(t1) /*t1的值放到t0*/
    STORE t0, 0(t2) /*t0的值放到t2*/
    addi s0, s0, -1 /*要恢複的寄存器個數-1*/
    addi t1, t1, 8 /*t1的位址加8*/
    addi t2, t2, 8 /*t2的位址加8*/
    bnez s0, copy_context_loop_interrupt /*如果s0不為0就重複拷貝*/

    la    s0, rt_interrupt_from_thread
    LOAD  s1, 0(s0)
    STORE sp, 0(s1)     /*更新from線程的sp指針*/

    la    s0, rt_interrupt_to_thread
    LOAD  s1, 0(s0)
    LOAD  sp, 0(s1)     /*恢複to線程的sp*/

    #ifdef RT_USING_USERSPACE
        mv a0, s1
        jal rt_thread_sp_to_thread
        jal lwp_mmu_switch /*切換mmu,函數内部會判斷from線程和to線程是不是在同一個lwp中,不是的話就會切換MMU*/
    #endif

spurious_interrupt:
    RESTORE_ALL /*恢複寄存器*/
    sret /*超級使用者模式異常傳回指令*/