iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 17
0
Software Development

系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!系列 第 17

Day 17: 深入淺出 RISC-V 源碼剖析 (2) - Target Status Control

  • 分享至 

  • xImage
  •  

0. 前言

經過上篇程式碼塞好、塞滿後~~!
今天還是要繼續這個主題下去,主要剖析RISC-V在Target Status Control相關的程式碼!
  
  
  

1. Halt (riscv_openocd_halt)

首先,先來看一下進入點的部分,
參考以下內容(src/target/riscv/riscv.c):

int riscv_openocd_halt(struct target *target)
{
    RISCV_INFO(r);

    LOG_DEBUG("halting all harts");

    int out = riscv_halt_all_harts(target);     ///譯註: 讓所有的Hart進入Halt
    if (out != ERROR_OK) {
        LOG_ERROR("Unable to halt all harts");
        return out;
    }

    register_cache_invalidate(target->reg_cache);
    if (riscv_rtos_enabled(target)) {
        target->rtos->current_threadid = r->rtos_hartid + 1;
        target->rtos->current_thread = r->rtos_hartid + 1;
    }

    target->state = TARGET_HALTED;  ///譯註: 設定目前的狀態為TARGET_HALTED
    target->debug_reason = DBG_REASON_DBGRQ;
    target_call_event_callbacks(target, TARGET_EVENT_HALTED);
    return out;
}

這部分比較簡單,主要就是呼叫riscv_halt_all_harts()
讓所有的Hart進入Halt的狀態!

所以接下來就是來看一下riscv_halt_all_harts()的內容(src/target/riscv/riscv.c):

int riscv_halt_all_harts(struct target *target)
{
    for (int i = 0; i < riscv_count_harts(target); ++i) {
        if (!riscv_hart_enabled(target, i))
            continue;

        riscv_halt_one_hart(target, i);     ///譯註: 針對單一個Hart設定進入Halt的狀態
    }

    return ERROR_OK;
}

這也很簡單,就是針對每一個Hart,呼叫riscv_halt_one_hart(),讓該Hart進入Halt,
所以一樣,這邊再來看一下riscv_halt_one_hart()的內容!

int riscv_halt_one_hart(struct target *target, int hartid)
{
    RISCV_INFO(r);
    LOG_DEBUG("halting hart %d", hartid);
    riscv_set_current_hartid(target, hartid);

    ///譯註: 判斷該Hart是否已經在Halt的狀態
    if (riscv_is_halted(target)) {
        LOG_DEBUG("  hart %d requested halt, but was already halted", hartid);
        return ERROR_OK;
    }

    return r->halt_current_hart(target);
}

一樣,這邊先簡單的判斷一下,假如該Hart已經在Halt的階段的話,
那就可以直接回傳ERROR_OK,不用再繼續往下處理!

最後,終於來到核心的處理部分,讓我們看一下halt_current_hart()是如何處理!

static int riscv013_halt_current_hart(struct target *target)
{
    RISCV_INFO(r);
    LOG_DEBUG("halting hart %d", r->current_hartid);
    if (riscv_is_halted(target))
        LOG_ERROR("Hart %d is already halted!", r->current_hartid);

    /* Issue the halt command, and then wait for the current hart to halt. */
    uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
    dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 1); ///譯註: 將haltreq拉成1,讓Target進入Halt
    dmi_write(target, DMI_DMCONTROL, dmcontrol);
    
    ///譯註: 做個檢查!
    for (size_t i = 0; i < 256; ++i)
        if (riscv_is_halted(target))
            break;

    if (!riscv_is_halted(target)) {
        uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
        dmcontrol = dmi_read(target, DMI_DMCONTROL);

        LOG_ERROR("unable to halt hart %d", r->current_hartid);
        LOG_ERROR("  dmcontrol=0x%08x", dmcontrol);
        LOG_ERROR("  dmstatus =0x%08x", dmstatus);
        return ERROR_FAIL;
    }

    dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 0);
    dmi_write(target, DMI_DMCONTROL, dmcontrol);

    return ERROR_OK;
}

有沒有覺得這個有點熟悉啊!?
我們在「Day 10: RISC-V Debug Module (下篇)-Debug Module Registers」中的"# 2.1 0x10 dmcontrol: Debug Module Control"有提到過,如果需要讓該Hart進入Halt的階段,就是將$dmcontrol中的haltreq設定成1!

當然,設定完之後還要確認一下Hart是否有真得停下來,為了避免陷入無窮等待之中,
這邊預設會讀256次:

    for (size_t i = 0; i < 256; ++i)
        if (riscv_is_halted(target))
            break;

如果還是真的無法停下來,那就要進行錯誤處理(幾本上就是把狀態印出來到Log中):

    if (!riscv_is_halted(target)) {
        uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
        dmcontrol = dmi_read(target, DMI_DMCONTROL);

        LOG_ERROR("unable to halt hart %d", r->current_hartid);
        LOG_ERROR("  dmcontrol=0x%08x", dmcontrol);
        LOG_ERROR("  dmstatus =0x%08x", dmstatus);
        return ERROR_FAIL;
    }

最後,記得把haltreq放掉:

    dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 0);
    dmi_write(target, DMI_DMCONTROL, dmcontrol);

以上就是針對Halt Target的處理!
  
  
  

2. Resume (riscv_openocd_resume)

既然有Halt了,那當然就有Resume,一樣先來看一下進入點的部分,
參考以下內容(src/target/riscv/riscv.c):

int riscv_openocd_resume(
        struct target *target,
        int current,
        target_addr_t address,
        int handle_breakpoints,
        int debug_execution
) {
    LOG_DEBUG("resuming all harts");

    ///譯註: Resume可以在不同的地方開始,這邊先把要Resume的位置寫入$PC中
    if (!current)
        riscv_set_register(target, GDB_REGNO_PC, address);

    int out = riscv_resume_all_harts(target);     ///譯註: 讓所有的Hart進入Resume
    if (out != ERROR_OK) {
        LOG_ERROR("unable to resume all harts");
        return out;
    }

    register_cache_invalidate(target->reg_cache);
    target->state = TARGET_RUNNING;
    target_call_event_callbacks(target, TARGET_EVENT_RESUMED);
    return out;
}

跟Halt的方式類似,一樣是呼叫riscv_resume_all_harts()
讓所有的Hart進入Resume的狀態!

接下來就是要來研究一下riscv_resume_all_harts()的內容(src/target/riscv/riscv.c):

int riscv_resume_all_harts(struct target *target)
{
    for (int i = 0; i < riscv_count_harts(target); ++i) {
        if (!riscv_hart_enabled(target, i))
            continue;

        riscv_resume_one_hart(target, i);    ///譯註: 針對單一個Hart設定進入Resume的狀態
    }

    riscv_invalidate_register_cache(target);
    return ERROR_OK;
}

這也很簡單,就是針對每一個Hart,呼叫riscv_resume_one_hart(),讓該Hart進入Resume,
所以這邊再來看一下riscv_resume_one_hart()的內容,請參考(src/target/riscv/riscv.c):

int riscv_resume_one_hart(struct target *target, int hartid)
{
    RISCV_INFO(r);
    LOG_DEBUG("resuming hart %d", hartid);
    riscv_set_current_hartid(target, hartid);
    
    ///譯註: 判斷該Hart是否已經Resume
    if (!riscv_is_halted(target)) {
        LOG_DEBUG("  hart %d requested resume, but was already resumed", hartid);
        return ERROR_OK;
    }

    r->on_resume(target);
    return r->resume_current_hart(target);
}

一樣,這邊先簡單的判斷一下,假如該Hart已經Resume的話,
那就可以直接回傳ERROR_OK,不用再繼續往下處理,
這邊呼叫到兩個核心的處理on_resume()resume_current_hart()

首先是on_resume()的部分,請參考(src/target/riscv/riscv-013.c):

static void riscv013_on_resume(struct target *target)
{
    return riscv013_on_step_or_resume(target, false);
}

static void riscv013_on_step_or_resume(struct target *target, bool step)
{
    ///譯註: 執行Program Buffer
    struct riscv_program program;
    riscv_program_init(&program, target);
    riscv_program_fence_i(&program);
    if (riscv_program_exec(&program, target) != ERROR_OK)
        LOG_ERROR("Unable to execute fence.i");

    /* We want to twiddle some bits in the debug CSR so debugging works. */
    ///譯註: 針對$dcsr做處理
    uint64_t dcsr = riscv_get_register(target, GDB_REGNO_DCSR);
    dcsr = set_field(dcsr, CSR_DCSR_STEP, step);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKM, 1);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKS, 1);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKU, 1);
    riscv_set_register(target, GDB_REGNO_DCSR, dcsr);
}

可以發現到on_resume()主要是在Resume之前,做一些必要的設定!
首先是利用Program Buffer執行fence.i這個指令,將Instruction和Data做同步的處理,詳細資料可以參閱「The RISC-V Instruction Set Manual Volume I: User-Level ISA」的說明!

再來就是,在「Day 11: RISC-V Debug Introduction」中有提到過的$dcsr (忘記可以翻一下"# 5.1 0x7b0 dcsr: Debug Control and Status"),主要控制Hart在Debug Mode下的行為!

這邊主要有三點:

    dcsr = set_field(dcsr, CSR_DCSR_EBREAKM, 1);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKS, 1);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKU, 1);

分別是在M(Machine) Mode、S(Supervisor) Mode、U(User) Mode中,
如果收到"ebreak"指令的話,皆會回到Debug Mode!

最後,我們來看一下resume_current_hart()的處理,請參考(src/target/riscv/riscv-013.c):

static int riscv013_resume_current_hart(struct target *target)
{
    return riscv013_step_or_resume_current_hart(target, false);
}

static int riscv013_step_or_resume_current_hart(struct target *target, bool step)
{
    RISCV_INFO(r);
    LOG_DEBUG("resuming hart %d (for step?=%d)", r->current_hartid, step);
    if (!riscv_is_halted(target)) {
        LOG_ERROR("Hart %d is not halted!", r->current_hartid);
        return ERROR_FAIL;
    }

    struct riscv_program program;
    riscv_program_init(&program, target);
    riscv_program_fence_i(&program);
    if (riscv_program_exec(&program, target) != ERROR_OK)
        return ERROR_FAIL;

    /* Issue the resume command, and then wait for the current hart to resume. */
    uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
    dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 1);   ///譯註: 將resumereq拉成1,讓Target進入Resume
    dmi_write(target, DMI_DMCONTROL, dmcontrol);

    ///譯註: 一樣做個檢查!
    for (size_t i = 0; i < 256; ++i) {
        usleep(10);
        uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
        if (get_field(dmstatus, DMI_DMSTATUS_ALLRESUMEACK) == 0)
            continue;
        if (step && get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0)
            continue;

        dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 0);
        dmi_write(target, DMI_DMCONTROL, dmcontrol);
        return ERROR_OK;
    }

    uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
    dmcontrol = dmi_read(target, DMI_DMCONTROL);
    LOG_ERROR("unable to resume hart %d", r->current_hartid);
    LOG_ERROR("  dmcontrol=0x%08x", dmcontrol);
    LOG_ERROR("  dmstatus =0x%08x", dmstatus);

    if (step) {
        LOG_ERROR("  was stepping, halting");
        riscv013_halt_current_hart(target);
        return ERROR_OK;
    }

    return ERROR_FAIL;
}

主要分成三個部分,首先是將resumereq拉起來成1,
忘記的話可以參考「Day 10: RISC-V Debug Module (下篇)-Debug Module Registers」中的"# 2.1 0x10 dmcontrol: Debug Module Control"!

再過來一樣,做個檢查

    for (size_t i = 0; i < 256; ++i) {
        usleep(10);
        uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
        if (get_field(dmstatus, DMI_DMSTATUS_ALLRESUMEACK) == 0)
            continue;
        if (step && get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0)
            continue;

        dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 0);
        dmi_write(target, DMI_DMCONTROL, dmcontrol);
        return ERROR_OK;
    }

看看Hart有沒有Resume成功!

最後就是假如沒有Resume成功的話,就進行錯誤處理!

    uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
    dmcontrol = dmi_read(target, DMI_DMCONTROL);
    LOG_ERROR("unable to resume hart %d", r->current_hartid);
    LOG_ERROR("  dmcontrol=0x%08x", dmcontrol);
    LOG_ERROR("  dmstatus =0x%08x", dmstatus);

以上就是針對Resume Target的部份!
  
  

3. Step (riscv_openocd_step)

其實Step就是在Resume完後,CPU執行一道指令後就立刻進入Debug Mode中,
因此在實作中,可以共同使用大部分的處理流程!

一樣,先看進入點,參考以下內容(src/target/riscv/riscv.c):

int riscv_openocd_step(
        struct target *target,
        int current,
        target_addr_t address,
        int handle_breakpoints
) {
    LOG_DEBUG("stepping rtos hart");

    ///譯註: 同Resuem,Step可以在不同的地方開始,
    ///      這邊先把要Step的位置寫入$PC中
    if (!current)
        riscv_set_register(target, GDB_REGNO_PC, address);

    int out = riscv_step_rtos_hart(target);     ///譯註: 讓該Hart進行Step
    if (out != ERROR_OK) {
        LOG_ERROR("unable to step rtos hart");
        return out;
    }

    register_cache_invalidate(target->reg_cache);
    target->state = TARGET_RUNNING;             ///譯註: 先將狀態設成RUNNING
    target_call_event_callbacks(target, TARGET_EVENT_RESUMED);
    target->state = TARGET_HALTED;              ///譯註: 然後再次進入HALTED的狀態
    target->debug_reason = DBG_REASON_SINGLESTEP;
    target_call_event_callbacks(target, TARGET_EVENT_HALTED);
    return out;
}

主要就是呼叫riscv_step_rtos_hart()!
再過來研究一下riscv_step_rtos_hart()的內容,請參考(src/target/riscv/riscv.c):

int riscv_step_rtos_hart(struct target *target)
{
    RISCV_INFO(r);
    int hartid = r->current_hartid;
    if (riscv_rtos_enabled(target)) {
        hartid = r->rtos_hartid;
        if (hartid == -1) {
            LOG_USER("GDB has asked me to step \"any\" thread, so I'm stepping hart 0.");
            hartid = 0;
        }
    }
    riscv_set_current_hartid(target, hartid);
    LOG_DEBUG("stepping hart %d", hartid);

    ///譯註: 判斷該Hart是否已經在Halt的狀態
    if (!riscv_is_halted(target)) {
        LOG_ERROR("Hart isn't halted before single step!");
        return ERROR_FAIL;
    }
    riscv_invalidate_register_cache(target);
    r->on_step(target);
    if (r->step_current_hart(target) != ERROR_OK)
        return ERROR_FAIL;
    riscv_invalidate_register_cache(target);
    r->on_halt(target);
    if (!riscv_is_halted(target)) {
        LOG_ERROR("Hart was not halted after single step!");
        return ERROR_FAIL;
    }
    return ERROR_OK;
}

由於在Resume(Running)的狀態中,就不需要進行Step,
因此會先判斷一下這種狀況:

    if (!riscv_is_halted(target)) {
        LOG_ERROR("Hart isn't halted before single step!");
        return ERROR_FAIL;
    }

最後呼叫到兩個核心的處理on_step()step_current_hart()來處理Step的流程
一樣,先來看看on_step()處理的過程,請參考(src/target/riscv/riscv-013.c):

static void riscv013_on_resume(struct target *target)
{
    return riscv013_on_step_or_resume(target, false);
}

static void riscv013_on_step_or_resume(struct target *target, bool step)
{
    struct riscv_program program;
    riscv_program_init(&program, target);
    riscv_program_fence_i(&program);
    if (riscv_program_exec(&program, target) != ERROR_OK)
        LOG_ERROR("Unable to execute fence.i");

    /* We want to twiddle some bits in the debug CSR so debugging works. */
    uint64_t dcsr = riscv_get_register(target, GDB_REGNO_DCSR);
    dcsr = set_field(dcsr, CSR_DCSR_STEP, step);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKM, 1);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKS, 1);
    dcsr = set_field(dcsr, CSR_DCSR_EBREAKU, 1);
    riscv_set_register(target, GDB_REGNO_DCSR, dcsr);
}

等等等~~~ 根本就跟on_resume()一樣!!!

主要差異就是在

dcsr = set_field(dcsr, CSR_DCSR_STEP, step);

在step的過程中,$dcsr的step會被設定成1,
忘記的話可以參考一下「Day 11: RISC-V Debug Introduction」中的"# 5.1 0x7b0 dcsr: Debug Control and Status"

最後,step_current_hart()處理的過程,跟上面Resume中類似,
請參考(src/target/riscv/riscv-013.c):

static int riscv013_step_current_hart(struct target *target)
{
    return riscv013_step_or_resume_current_hart(target, true);
}

static int riscv013_step_or_resume_current_hart(struct target *target, bool step)
{
    RISCV_INFO(r);
    LOG_DEBUG("resuming hart %d (for step?=%d)", r->current_hartid, step);
    if (!riscv_is_halted(target)) {
        LOG_ERROR("Hart %d is not halted!", r->current_hartid);
        return ERROR_FAIL;
    }

    struct riscv_program program;
    riscv_program_init(&program, target);
    riscv_program_fence_i(&program);
    if (riscv_program_exec(&program, target) != ERROR_OK)
        return ERROR_FAIL;

    /* Issue the resume command, and then wait for the current hart to resume. */
    uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
    dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 1);
    dmi_write(target, DMI_DMCONTROL, dmcontrol);

    for (size_t i = 0; i < 256; ++i) {
        usleep(10);
        uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
        if (get_field(dmstatus, DMI_DMSTATUS_ALLRESUMEACK) == 0)
            continue;
        if (step && get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0)
            continue;

        dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 0);
        dmi_write(target, DMI_DMCONTROL, dmcontrol);
        return ERROR_OK;
    }

    uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
    dmcontrol = dmi_read(target, DMI_DMCONTROL);
    LOG_ERROR("unable to resume hart %d", r->current_hartid);
    LOG_ERROR("  dmcontrol=0x%08x", dmcontrol);
    LOG_ERROR("  dmstatus =0x%08x", dmstatus);

    if (step) {
        LOG_ERROR("  was stepping, halting");
        riscv013_halt_current_hart(target);
        return ERROR_OK;
    }

    return ERROR_FAIL;
}

主要差異再多做一些檢查:

if (step && get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0)
            continue;

在Resume後看看有沒有重新進入Halted!
如果沒有的話,一樣,還是有錯誤處理:

    if (step) {
        LOG_ERROR("  was stepping, halting");
        riscv013_halt_current_hart(target);
        return ERROR_OK;
    }

以上就是針對Step Target的部份!
  
  
  

4. Reset (riscv_assert_reset/riscv_deassert_reset)

最後是Reset的部分啦!!
在OpenOCD中,Reset的流程主要分成兩步驟:

  • Assert reset
  • Deassert reset

首先是進入點的部份,兩個一起看,請參考(src/target/riscv/riscv.c):

static int riscv_assert_reset(struct target *target)
{
    struct target_type *tt = get_target_type(target);
    return tt->assert_reset(target);
}

static int riscv_deassert_reset(struct target *target)
{
    LOG_DEBUG("RISCV DEASSERT RESET");
    struct target_type *tt = get_target_type(target);
    return tt->deassert_reset(target);
}

就是簡單的呼叫0.13中的assert_reset()deassert_reset()來處理!
這部分內容在「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」中有稍微提到過,
這邊再剖析的更仔細些!!
  
  

4.1 Assert Reset

一樣是先看一下實作的部分,請參考(src/target/riscv/riscv-013.c):

static int assert_reset(struct target *target)
{
    RISCV_INFO(r);

    select_dmi(target);

    uint32_t control_base = set_field(0, DMI_DMCONTROL_DMACTIVE, 1);

    if (target->rtos) {
        ....中間省略!

    } else {
        /* Reset just this hart. */
        uint32_t control = set_field(control_base, DMI_DMCONTROL_HARTSEL,
                r->current_hartid);
        control = set_field(control, DMI_DMCONTROL_HALTREQ,
                target->reset_halt ? 1 : 0);
        control = set_field(control, DMI_DMCONTROL_HARTRESET, 1);   ///譯註: 這邊試這先拉起hartreset看看~!
        dmi_write(target, DMI_DMCONTROL, control);

        /* Read back to check if hartreset is supported. */
        uint32_t rb = dmi_read(target, DMI_DMCONTROL);
        if (!get_field(rb, DMI_DMCONTROL_HARTRESET)) {
            /* Use ndmreset instead. That will reset the entire device, but
             * that's probably what OpenOCD wants anyway. */
            control = set_field(control, DMI_DMCONTROL_HARTRESET, 0);

            ///譯註: 改用ndmreset重試!
            control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
            dmi_write(target, DMI_DMCONTROL, control);
        }
    }

    ///譯註: 將目前Target的狀態設成TARGET_RESET
    target->state = TARGET_RESET;

    return ERROR_OK;
}

在Debug System中,有兩個方式讓Target可以做Reset

  • ndmreset: non-debug module reset,主要將Debug Module和Debug Transport Modules以外的裝置、Hart全部進行Reset!
  • hartreset: hart reset,主要用來針對目前選定的Hart進行Reset!

在這個實作當中,會先試著用hartreset來做然後重新讀回$dmcontrol中的hartreset!
看看是否可以寫入,如果不行寫入,表示硬體不支援這種方式,
那就改用ndmreset的方式來進行!

當然,假如是reset halt這種情況的時候,記得把haltreq拉起來:

        control = set_field(control, DMI_DMCONTROL_HALTREQ,
                target->reset_halt ? 1 : 0);

  
  

4.1 Deassert Reset

在把Reset拉起來後,當然要把拉放回去,所以就需要進行Deassert Reset!
一樣,先看實作的部分!

static int deassert_reset(struct target *target)
{
    RISCV_INFO(r);
    RISCV013_INFO(info);
    select_dmi(target);

    LOG_DEBUG("%d", r->current_hartid);

    /* Clear the reset, but make sure haltreq is still set */
    uint32_t control = 0;
    control = set_field(control, DMI_DMCONTROL_HALTREQ, target->reset_halt ? 1 : 0);    ///譯註: Reset歸回原位,但是如果有用到haltreq的話,不能先放到!
    control = set_field(control, DMI_DMCONTROL_HARTSEL, r->current_hartid);
    control = set_field(control, DMI_DMCONTROL_DMACTIVE, 1);
    dmi_write(target, DMI_DMCONTROL, control);

    uint32_t dmstatus;
    int dmi_busy_delay = info->dmi_busy_delay;
    time_t start = time(NULL);

    if (target->reset_halt) {
        LOG_DEBUG("Waiting for hart to be halted.");
        do {
            dmstatus = dmi_read(target, DMI_DMSTATUS);
            if (time(NULL) - start > riscv_reset_timeout_sec) {
                LOG_ERROR("Hart didn't halt coming out of reset in %ds; "
                        "dmstatus=0x%x; "
                        "Increase the timeout with riscv set_reset_timeout_sec.",
                        riscv_reset_timeout_sec, dmstatus);
                return ERROR_FAIL;
            }
            target->state = TARGET_HALTED;
        } while (get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0);

        control = set_field(control, DMI_DMCONTROL_HALTREQ, 0);
        dmi_write(target, DMI_DMCONTROL, control);

    } else {
        LOG_DEBUG("Waiting for hart to be running.");
        do {
            dmstatus = dmi_read(target, DMI_DMSTATUS);
            if (get_field(dmstatus, DMI_DMSTATUS_ANYHALTED) ||
                    get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL)) {
                LOG_ERROR("Unexpected hart status during reset. dmstatus=0x%x",
                        dmstatus);
                return ERROR_FAIL;
            }
            if (time(NULL) - start > riscv_reset_timeout_sec) {
                LOG_ERROR("Hart didn't run coming out of reset in %ds; "
                        "dmstatus=0x%x; "
                        "Increase the timeout with riscv set_reset_timeout_sec.",
                        riscv_reset_timeout_sec, dmstatus);
                return ERROR_FAIL;
            }
        } while (get_field(dmstatus, DMI_DMSTATUS_ALLRUNNING) == 0);
        target->state = TARGET_RUNNING;
    }
    info->dmi_busy_delay = dmi_busy_delay;
    return ERROR_OK;
}

分成三部分來解釋!
首先,在Reset完後,先把Reset歸回原位,但是同時,
如果有用到haltreq的話(reset halt),則不能在此時放掉haltreq(至少要等到確認後才行)!

    /* Clear the reset, but make sure haltreq is still set */
    uint32_t control = 0;
    control = set_field(control, DMI_DMCONTROL_HALTREQ, target->reset_halt ? 1 : 0);
    control = set_field(control, DMI_DMCONTROL_HARTSEL, r->current_hartid);
    control = set_field(control, DMI_DMCONTROL_DMACTIVE, 1);
    dmi_write(target, DMI_DMCONTROL, control);

接著分成兩部分來處理,首先是reset halt的時候:

        LOG_DEBUG("Waiting for hart to be halted.");
        do {
            dmstatus = dmi_read(target, DMI_DMSTATUS);
            if (time(NULL) - start > riscv_reset_timeout_sec) {
                LOG_ERROR("Hart didn't halt coming out of reset in %ds; "
                        "dmstatus=0x%x; "
                        "Increase the timeout with riscv set_reset_timeout_sec.",
                        riscv_reset_timeout_sec, dmstatus);
                return ERROR_FAIL;
            }
            target->state = TARGET_HALTED;  ///譯註: 將Target的設定成TARGET_RUNNING
        } while (get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0);

        ///譯註: 放掉haltreq
        control = set_field(control, DMI_DMCONTROL_HALTREQ, 0);
        dmi_write(target, DMI_DMCONTROL, control);

為了確認所有Target都正確停下來,因此讀取$dmstatus中的allhalted這個欄位!
確認停下來後,就可以把haltreq歸位:

        control = set_field(control, DMI_DMCONTROL_HALTREQ, 0);
        dmi_write(target, DMI_DMCONTROL, control);

再來是針對reset run的這個部份:

        LOG_DEBUG("Waiting for hart to be running.");
        do {
            dmstatus = dmi_read(target, DMI_DMSTATUS);
            if (get_field(dmstatus, DMI_DMSTATUS_ANYHALTED) ||
                    get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL)) {
                LOG_ERROR("Unexpected hart status during reset. dmstatus=0x%x",
                        dmstatus);
                return ERROR_FAIL;
            }
            if (time(NULL) - start > riscv_reset_timeout_sec) {
                LOG_ERROR("Hart didn't run coming out of reset in %ds; "
                        "dmstatus=0x%x; "
                        "Increase the timeout with riscv set_reset_timeout_sec.",
                        riscv_reset_timeout_sec, dmstatus);
                return ERROR_FAIL;
            }
        } while (get_field(dmstatus, DMI_DMSTATUS_ALLRUNNING) == 0);
        target->state = TARGET_RUNNING; ///譯註: 將Target的設定成TARGET_RUNNING

一樣,為了確認所有Target都正確運行中,因此讀取$dmstatus中的allrunning這個欄位!
確認都Target都正在賣力地跑後,就可以將Target的狀態設定成TARGET_RUNNING

假如發現$dmstatus中的anyhalted或是anyunavail的時候,就表示Reset已經造成Target出現問題,因此要進行相關處理(打印Log XD!?):

            if (get_field(dmstatus, DMI_DMSTATUS_ANYHALTED) ||
                    get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL)) {
                LOG_ERROR("Unexpected hart status during reset. dmstatus=0x%x",
                        dmstatus);
                return ERROR_FAIL;
            }

以上就是針對Reset Target的處理!
  
  
  

99. 結語

今天的程式碼又是塞好塞滿!
簡單的介紹了Target相關的處理: Halt、Resume+Step、Reset!

感覺打一篇的時間越花越多~~~!
  
  
  

參考資料

  1. RISC-V External Debug Support 0.13
  2. GitHub: riscv/riscv-openocd
  3. Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control
  4. Day 10: RISC-V Debug Module (下篇)-Debug Module Registers
  5. Day 11: RISC-V Debug Introduction

上一篇
Day 16: 深入淺出 RISC-V 源碼剖析 (1) - Overview & Init
下一篇
Day 18: 深入淺出 RISC-V 源碼剖析 (3) - Register Access
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言