iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 10
0

0. 前言

我的媽呀~~ 終於過了三分之一!
上篇簡單的介紹了Debug Module中核心功能: Program Buffer和Abstract Commands,
那Debug Module基本上也都差不多講完了!
本篇主要是介紹Debug Module中常用到的Registers和其中每個Bits所代表的意義,
Debugger就是透過對這些Register的讀/寫來達到控制整個Target的動作!
以下內容比較繁瑣一些,基本上就跟RISC-V External Debug Support 0.13 的內容差不多,只不過是中文的XD!
  
  
  

1. Debug Module Registers Overview

本文主要會介紹以下幾個Registers

  • 0x10: Debug Module Control
  • 0x11: Debug Module Status
  • 0x16: Abstract Control and Status
  • 0x17: Abstract Command
      
      
      

2. Debug Module Registers Detailed

硬是要分成兩節~~~~
  
  

2.1 0x10 dmcontrol: Debug Module Control

https://ithelp.ithome.com.tw/upload/images/20171228/20107327l3IIhs1NKT.png
---引用自RISC-V External Debug Support 0.13

  • haltreq: Halt request,當設定成1時,Debug Module會讓Target進入Halt的階段
    • 如果Target已經Halt的話,在重複設定會無效!
    • Resume之前,必須清成0
  • resumereq: Resume request,當Target在Halted的話,設成1會讓Target進行Resume(Running)
    • 如果haltreq和resumereq同時設定成1的話,以haltreq為準!
  • hartreset: 這是可選的功能,設定成1的話會讓Debug Module拉起Reset Signal並將Target進行Reset,記得之後清成0,讓Reset Signal放下!
  • ackhavereset: 這是新功能,可選的部分
    • 當dmstatus的havereset被設成1的時後,必須靠這邊寫入1才能清掉!
    • 用意是讓Debugger當作ACK用,這部分也還沒有實作!
  • hasel和hartsel: 主要是讓Debug Module可以選擇作用的Target是一個還是多個目標
    • 目前沒有實做出來~~!
  • ndmreset: 「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」中有提過,用來當作Global的Reset Signal,讓除了Debug Module以外的整個System進行Reset
  • dmactive: 「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」中也有提過,用來將Debug Module自己進行Reset!

以下是幾個簡單的實作用來說明如何使用這個Register,
首先是haltreq,請參考(src/target/riscv/riscv-013.c)

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);
    
    ///譯註: 將haltreq拉成1,讓Target進入Halt
    dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 1);
    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;
    }

    ///譯註: 確認Target進入Halt階段後,記得清掉haltreq!!
    dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 0);
    dmi_write(target, DMI_DMCONTROL, dmcontrol);

    return ERROR_OK;
}

再來是resumereq,請參考(src/target/riscv/riscv-013.c)

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. */
    ///譯註: 將resumereq拉成1,讓Target進入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;
    }

    ....後面省略!
}

hartreset和ndmreset的實作可以參考「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」中的"# 2.1 Reset Control"的部分,這邊不多加贅述!

最後是dmactive的部分,請參考(src/target/riscv/riscv-013.c)

static int examine(struct target *target)
{
    ....前面不重要,省略

    /* Reset the Debug Module. */
    dmi_write(target, DMI_DMCONTROL, 0);
    dmi_write(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE);
    uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);

    ....中間不重要,省略

    if (!get_field(dmcontrol, DMI_DMCONTROL_DMACTIVE)) {
        LOG_ERROR("Debug Module did not become active. dmcontrol=0x%x",
                dmcontrol);
        return ERROR_FAIL;
    }
    
    ....後面不重要,省略
}

  
  

2.2 0x11 dmstatus: Debug Module Status

https://ithelp.ithome.com.tw/upload/images/20171228/20107327Oa4RfCA5po.png
---引用自RISC-V External Debug Support 0.13

乍看之下覺得會很複雜,但仔細看一下,他都是all加上any兩兩一組的,
底下也按照這樣分類解說!

  • dmerr: 用來判斷Debug Module有沒有存取錯誤的地方,這部分目前沒有實作!
  • impebreak: 當此值為1的時候,表示Program Buffer最後面不用自己加上"ebreak"。多出來的空間可以拿來放其他要執行的任意指令
    • 可以試想一下,如果沒有這個功能,但Program Buffer大小只有一道指令的話,那基本上Program Buffer也廢了,因為根本沒辦法放入其他指令,光放上"ebreak"就滿了XD
  • allhavereset+anyhavereset: 用來表示Hart曾經發生過Reset
    • Debugger作為ACK回應,則應該清掉dmcontrol中的ackhavereset,表示Debugger知道了這件事!
    • 新功能,這部分還沒有實作!
  • allresumeack+anyresumeack: 用來標示Hart知道要進入Resume狀態中!
  • allnonexistent+anynonexistent: 用來標示目前選擇的Hart並不存在!
  • allunavail+anyunavail: 用來標示Hart為Unavailable
    • 比方說Reset後,Hart變成Unavailable
  • allrunning+anyrunning: 不用特別說明吧XD,表示Hart正在賣力地執行當中!
  • allhalted+anyhalted: 用來標示Hart目前處在Halt的階段等待中!
  • authenticated: 針對Debug Module的安全性,當設成0表示Debugger必須通過驗證(通常是一串密碼),才能使用Debug Module,避免未經驗證的存取!
  • authbusy: 用來標示安全驗證的模組是否可以寫入,目前沒看到有實作!
  • devtreevalid: 新東西,我也不知道這是幹啥用的(囧rz),目前也沒看到有實作!
  • version: 用來標示Debug Module的版本!
    • 0: Debug Module不存在
    • 1: Debug Module版本為0.11,這個Spec我們不會提到
    • 2: Debug Module版本為0.13,就是你現在正在看的東西XD!

以下是一個簡單的實作,用來得知Hart目前的狀況!
請參考(src/target/riscv/riscv-013.c)

static int examine(struct target *target)
{
    ....前面不重要,省略

    ///譯註: 這邊判斷Debug Module的版本是否為0.13
    uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
    if (get_field(dmstatus, DMI_DMSTATUS_VERSION) != 2) {
        LOG_ERROR("OpenOCD only supports Debug Module version 2, not %d "
                "(dmstatus=0x%x)", get_field(dmstatus, DMI_DMSTATUS_VERSION), dmstatus);
        return ERROR_FAIL;
    }

    /* Reset the Debug Module. */
    dmi_write(target, DMI_DMCONTROL, 0);
    dmi_write(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE);
    uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);

    ....中間不重要,省略

    if (!get_field(dmcontrol, DMI_DMCONTROL_DMACTIVE)) {
        LOG_ERROR("Debug Module did not become active. dmcontrol=0x%x",
                dmcontrol);
        return ERROR_FAIL;
    }

    if (!get_field(dmstatus, DMI_DMSTATUS_AUTHENTICATED)) {
        LOG_ERROR("Authentication required by RISC-V core but not "
                "supported by OpenOCD. dmcontrol=0x%x", dmcontrol);
        return ERROR_FAIL;
    }

    if (get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL)) {
        LOG_ERROR("The hart is unavailable.");
        return ERROR_FAIL;
    }

    if (get_field(dmstatus, DMI_DMSTATUS_ANYNONEXISTENT)) {
        LOG_ERROR("The hart doesn't exist.");
        return ERROR_FAIL;
    }

    ....後面不重要,省略
}

  
  

2.3 0x16 abstractcs: Abstract Control and Status

https://ithelp.ithome.com.tw/upload/images/20171228/20107327SxXkhFaE5W.png
---引用自RISC-V External Debug Support 0.13

這個Register是用來標示"Program Buffer"和"Abstract Command"的一些資本資訊和狀態用的!

  • progbufsize: 標示Program Buffer的Words總數(大小),一個Word的大小為32-bits,其值為0~16!
  • busy: 當狀態為1的時候,表示目前Abstract Command還在執行中!
  • cmderr: 用來表示Abstract Command是否執行成功
    • 0(none): 沒有任何問題,恭喜!
    • 1(busy): command、data Register還在忙碌中!
    • 2(not support): 恭喜! 你用到了一個不支援的Abstract Command(有可能是這個Abstract Command只支援在Hart為Halt的時候才能執行)!
    • 3(exception): 靠北邊走~~ 出錯囉! 通常是Program Buffer裡面用到不支援的指令,或是指令用錯方法!
    • 4(halt/resume): Abstract Command不能在Hart目前的狀態下執行
    • 7(other): 反正不知道是為啥,出錯就是了....囧
  • datacount: 用來標示Debug Module中可以用來讓Abstract Command存放Data的Registers得數量!

實作的部分放在下面"# 2.4 0x17: Abstract Command"一起看!!
  
  

2.4 0x17 command: Abstract Command

https://ithelp.ithome.com.tw/upload/images/20171228/20107327QZKVQV79Nt.png
---引用自RISC-V External Debug Support 0.13

這部分的內容在「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」中的"# 2. Abstract Commands Overview"有提到過,基本上將Command組好後,寫入這個Register中就會開始執行!

連同上面「0x16 abstractcs: Abstract Control and Status」一起看!
請參考(src/target/riscv/riscv-013.c)

static int wait_for_idle(struct target *target, uint32_t *abstractcs)
{
    RISCV013_INFO(info);
    time_t start = time(NULL);
    while (1) {
        *abstractcs = dmi_read(target, DMI_ABSTRACTCS);

        ///譯註: 判斷是否還在執行中!
        if (get_field(*abstractcs, DMI_ABSTRACTCS_BUSY) == 0)
            return ERROR_OK;

        ///譯註: 這邊有加上timeout的機制,避免Abstract Command一直執行不完!
        if (time(NULL) - start > riscv_command_timeout_sec) {
            info->cmderr = get_field(*abstractcs, DMI_ABSTRACTCS_CMDERR);
            if (info->cmderr != CMDERR_NONE) {
                const char *errors[8] = {
                    "none",
                    "busy",
                    "not supported",
                    "exception",
                    "halt/resume",
                    "reserved",
                    "reserved",
                    "other" };

                LOG_ERROR("Abstract command ended in error '%s' (abstractcs=0x%x)",
                        errors[info->cmderr], *abstractcs);
            }

            LOG_ERROR("Timed out after %ds waiting for busy to go low (abstractcs=0x%x). "
                    "Increase the timeout with riscv set_command_timeout_sec.",
                    riscv_command_timeout_sec,
                    *abstractcs);
            return ERROR_FAIL;
        }
    }
}

static int execute_abstract_command(struct target *target, uint32_t command)
{
    RISCV013_INFO(info);
    LOG_DEBUG("command=0x%x", command);

    ///譯註: 將組合好的Command法送出去執行
    dmi_write(target, DMI_COMMAND, command);

    {
        uint32_t dmstatus = 0;

        ///譯註: 這邊會等待執行完畢!
        wait_for_idle(target, &dmstatus);
    }

    ///譯註: 確認Abstract Command是否有發生過錯誤!
    uint32_t cs = dmi_read(target, DMI_ABSTRACTCS);
    info->cmderr = get_field(cs, DMI_ABSTRACTCS_CMDERR);
    if (info->cmderr != 0) {
        LOG_DEBUG("command 0x%x failed; abstractcs=0x%x", command, cs);
        /* Clear the error. */
        dmi_write(target, DMI_ABSTRACTCS, set_field(0, DMI_ABSTRACTCS_CMDERR,
                    info->cmderr));
        return ERROR_FAIL;
    }

    return ERROR_OK;
}

  
  
  

3. Overview of States

介紹完上面的部分後,最後來回顧一下Target的States部分,
並把「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」和「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」做個總結的回顧!

先上圖 (他終於是彩色的~~~

https://ithelp.ithome.com.tw/upload/images/20171228/20107327sqkOmPRjvB.png
---引用自RISC-V External Debug Support 0.13
  
  

3.1 Target Status Control

首先看到中間 綠色框起來 的地方,當Hart踩到Breakpoint/Watchpoint/ebreak等等進入Debug Mode或是Debugger將dmcontrol的halreq拉起來的時候,Hart就會進入"Halting"的階段,確定停下來後,就將dmstatus的allhalted拉成1,進入"Halting: Waiting",等待Debugger的介入!

Resum也是同樣的方法,當Debugger將dmcontrol的resumereq拉起來後,Target會進入"Resuming"的狀態,直到確認開始Runing後,就將dmstatus的allrunning拉成1,此時就回到"M/U/S Mode(Runing)"!

參考以下實作方式(src/target/riscv/riscv-013.c)

int riscv013_execute_debug_buffer(struct target *target)
{
    uint32_t run_program = 0;
    run_program = set_field(run_program, AC_ACCESS_REGISTER_SIZE, 2);
    run_program = set_field(run_program, AC_ACCESS_REGISTER_POSTEXEC, 1);///譯註: 重點是這邊postexec要設定成1
    run_program = set_field(run_program, AC_ACCESS_REGISTER_TRANSFER, 0);
    run_program = set_field(run_program, AC_ACCESS_REGISTER_REGNO, 0x1000);

    return execute_abstract_command(target, run_program);
}

  
  

3.2 Abstract Command: Access Register & Program Buffer Executing

再來看到 右下紅色框起來 的地方,當在Halted的階段中,如果對commnd寫入,並將command的cmdtype設定成"0",則會進入到"Command: Start"這個階段,此時分成兩種功能:

  1. Program Buffer Executing
  2. Access Register

先講Program Buffer Executing的地方,當command中的postexec設定成1且transfer設成0,則會進入"Command: ProgBuf",並開始執行整個Program Buffer,當Program Buffer的最後一條指令"ebreak"執行後,就會回到"Commmand: Done"的階段,整個Program Buffer的執行就告一段落!

再來是Access Register的部分,當command中的transfer設定成1時候,就會進入到"Command: Transfer"的階段,並依照command中的write和regno來決定讀/寫和目標的Register,
詳細操作可以參考: Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands!
同樣的,當完成後,就會回到"Commmand: Done"的階段,整個Access Register的執行就算告一段落!

程式碼的部分Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands中有說明過,不過好像忘記放上command組成的部分,這邊補上~~!
參考以下實作方式(src/target/riscv/riscv-013.c)

static uint32_t access_register_command(uint32_t number, unsigned size,
        uint32_t flags)
{
    ///譯註: cmdtype設成0=>Access Register
    uint32_t command = set_field(0, DMI_COMMAND_CMDTYPE, 0);

    ///譯註: 按照Register的長度,決定size
    switch (size) {
        case 32:
            command = set_field(command, AC_ACCESS_REGISTER_SIZE, 2);
            break;
        case 64:
            command = set_field(command, AC_ACCESS_REGISTER_SIZE, 3);
            break;
        default:
            assert(0);
    }

    ///譯註: 依照目標的Register,決定regno
    if (number <= GDB_REGNO_XPR31) {
        command = set_field(command, AC_ACCESS_REGISTER_REGNO,
                0x1000 + number - GDB_REGNO_ZERO);
    } else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
        command = set_field(command, AC_ACCESS_REGISTER_REGNO,
                0x1020 + number - GDB_REGNO_FPR0);
    } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
        command = set_field(command, AC_ACCESS_REGISTER_REGNO,
                number - GDB_REGNO_CSR0);
    } else {
        assert(0);
    }

    command |= flags;

    return command;
}

  
  

3.3 Abstract Command: Quick Access

這部分目前還有看到有實作的地方,先跳過~!!!!!!
日後官方有完成的話,再回來填這個坑!
  
  

99. 結語

終於將整個Debug Module稍微的介紹完畢!!
希望能夠帶給讀者了解整個Debug系統是如何運作的!!
明天將更深入Hart中,看看在Hart中的Debug Mode是如何運作!
  
  
  

參考資料

  1. RISC-V External Debug Support 0.13
  2. GitHub: riscv/riscv-openocd

上一篇
Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands
下一篇
Day 11: RISC-V Debug Introduction
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30

尚未有邦友留言

立即登入留言