iT邦幫忙

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

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

Day 16: 深入淺出 RISC-V 源碼剖析 (1) - Overview & Init

  • 分享至 

  • xImage
  •  

0. 前言

在上篇「Day 15: 讓百萬人都驚呆的Debug Transport Module~~(下)」中有提到"明天開始將進入到全新的領域--FTDI Based Adapter,
講述從PC端到Target端中間的Debug Trasnport Hardware的相關"!?

不過仔細想想才發現,好像還沒有介紹怎麼使用OpenOCD在RISC-V的架構中Debug!
而且前面從Day 07到Day 15都是很零碎的講解External Debug System~~
所以呢.... 從本篇開始,預計4~5篇文章,再加上1個簡單的Lab來剖析一下OpenOCD是如何支援RISC-V架構的Target!

那我們開始吧!
  
  
  

1. OpenOCD Target Support

在OpenOCD中,如果要先增Target,則必須先在src/target/Makefile.am中,
註冊實作Target的Source/Header檔案,
讓OpenOCD在編譯的時候可以順利找到你寫好的那些東西!

如以下範例(src/target/Makefile.am):


noinst_LTLIBRARIES += %D%/libtarget.la
%C%_libtarget_la_SOURCES = \
    ....前面省略
    $(RISCV_SRC) \
    ....後面省略


....中間其他平台的設定,省略!

RISCV_SRC = \
    %D%/riscv/riscv-011.c \
    %D%/riscv/riscv-013.c \
    %D%/riscv/riscv.c \
    %D%/riscv/program.c \
    %D%/riscv/batch.c

....後面省略

接著在src/target/target.c中註冊一個struct target_type <Target_Name>當作這個Target相關支援的進入點!

如以下範例(src/target/target.c):

extern struct target_type riscv_target;

static struct target_type *target_types[] = {
    ...前面省略...
    &nds32_v2_target,       /// 譯註: 硬是要廣告! 這是Andes V2 架構的進入點!!!
    &nds32_v3_target,       /// 譯註: 硬是要廣告! 這是Andes V3 架構的進入點!!!
    &nds32_v3m_target,      /// 譯註: 硬是要廣告! 這是Andes V3m 架構的進入點!!!
    ...中間省略...
    &riscv_target,          /// 譯註: 本文的重點!
    ...後面省略...
    NULL,
};

那這個進入點裡面包含什麼呢!?
讓我們參考以下內容(src/target/riscv/riscv.c):

struct target_type riscv_target = {
    .name = "riscv",

    .init_target = riscv_init_target,
    .deinit_target = riscv_deinit_target,
    .examine = riscv_examine,

    /* poll current target status */
    .poll = old_or_new_riscv_poll,

    .halt = old_or_new_riscv_halt,
    .resume = old_or_new_riscv_resume,
    .step = old_or_new_riscv_step,

    .assert_reset = riscv_assert_reset,
    .deassert_reset = riscv_deassert_reset,

    .read_memory = riscv_read_memory,
    .write_memory = riscv_write_memory,

    .blank_check_memory = riscv_blank_check_memory,
    .checksum_memory = riscv_checksum_memory,

    .get_gdb_reg_list = riscv_get_gdb_reg_list,

    .add_breakpoint = riscv_add_breakpoint,
    .remove_breakpoint = riscv_remove_breakpoint,

    .add_watchpoint = riscv_add_watchpoint,
    .remove_watchpoint = riscv_remove_watchpoint,

    .arch_state = riscv_arch_state,

    .run_algorithm = riscv_run_algorithm,

    .commands = riscv_command_handlers
};

你會發現,除了定義這個Target的名稱叫"riscv"外,還有連結了不少"Function Pointer"來指向實作的部分!

從本篇開始,預計4~5篇文章,將會分成以下幾類,分別詳細剖析原始碼:

  1. RISC-V Target Create(Init) & Examine
  2. RISC-V Target Status Control
  • Halt
  • Resume
  • Step
  • Reset
    • Assert Reset
    • Deassert Reset
  1. Add/Remove Breakpoint/Watchpoint
  2. RISC-V Target Polling & Debug(Halt) Reason
  3. ....其他有想到要補充的地方

最後再搭配一篇Lab用OpenOCD來操作RISC-V的小實驗!
  
  
  

2. RISC-V Target Init (riscv_init_target)

這邊比較簡單一些,主要是在OpenOCD讀到Config中"init"後,就會對每個Target去呼叫type->init_target()來做內部資料的一些初始化!

RISC-V Target初始化的方式如下(src/target/riscv/riscv.c):

static int riscv_init_target(struct command_context *cmd_ctx,
        struct target *target)
{
    LOG_DEBUG("riscv_init_target()");
    target->arch_info = calloc(1, sizeof(riscv_info_t));        ///譯註: 首先註冊一個riscv_info_t的arch_info用來儲存Target相關的一些資料
    if (!target->arch_info)
        return ERROR_FAIL;
    riscv_info_t *info = (riscv_info_t *) target->arch_info;
    riscv_info_init(target, info);      ///譯註: 初始化這個資料結構
    info->cmd_ctx = cmd_ctx;

    select_dtmcontrol.num_bits = target->tap->ir_length;
    select_dbus.num_bits = target->tap->ir_length;
    select_idcode.num_bits = target->tap->ir_length;

    return ERROR_OK;
}

其中

select_dtmcontrol.num_bits = target->tap->ir_length;
select_dbus.num_bits = target->tap->ir_length;
select_idcode.num_bits = target->tap->ir_length;

這部分如果之後文章中有提到JTAG底層操作的時候會再詳細提到這部分,這邊先跳過!!

OpenOCD允許Target在內部儲存自己所需要用到的所有變數和資料、輔助的函數等等,
並利用target->arch_info指向這個儲存的地方!
當然,這個資料也要初始化,所以我們來看看以下實作(src/target/riscv/riscv.c):

void riscv_info_init(struct target *target, riscv_info_t *r)
{
    memset(r, 0, sizeof(*r));
    r->dtm_version = 1;
    r->registers_initialized = false;
    r->current_hartid = target->coreid;

    memset(r->trigger_unique_id, 0xff, sizeof(r->trigger_unique_id));

    for (size_t h = 0; h < RISCV_MAX_HARTS; ++h) {
        r->xlen[h] = -1;    ///譯註: 假設所有Target皆是UNKNOWN-bits

        for (size_t e = 0; e < RISCV_MAX_REGISTERS; ++e)
            r->valid_saved_registers[h][e] = false;
    }
}

這只是內部資料處理的部分,當然,OpenOCD還沒有真正連上Target,
因此對於Target的相關資訊也還沒辦法知道!

因此在下一節中,將探討OpenOCD如何去偵測Target的必要資訊!
  
  
  

3. RISC-V Target Examine (riscv_examine)

不囉唆! 先上程式碼!
參考以下內容(src/target/riscv/riscv.c):

static int riscv_examine(struct target *target)
{
    LOG_DEBUG("riscv_examine()");
    if (target_was_examined(target)) {
        LOG_DEBUG("Target was already examined.");
        return ERROR_OK;
    }

    /* Don't need to select dbus, since the first thing we do is read dtmcontrol. */

    riscv_info_t *info = (riscv_info_t *) target->arch_info;

    ///譯註: 利用dtmcontrol_scan來得到DTM中$dtmsc的值!
    uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
    LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
    info->dtm_version = get_field(dtmcontrol, DTMCONTROL_VERSION);  ///譯註: 判斷Debug System的版本0.11/0.13/...!
    LOG_DEBUG("  version=0x%x", info->dtm_version);

    struct target_type *tt = get_target_type(target);
    if (tt == NULL)
        return ERROR_FAIL;

    int result = tt->init_target(info->cmd_ctx, target);
    if (result != ERROR_OK)
        return result;

    return tt->examine(target);
}

還記得「Day 14: 讓百萬人都驚呆的Debug Transport Module~~(上)」中介紹到的$dtmsc嗎XD!?

忘記的話可以參考一下"2.2 0x10 dtmsc: DTM Control and Status"的部分!!

在這邊中,最重要的是讀取出version這個欄位的資料,因為在實作中分成0.11(riscv-011.c)和0.13(riscv-013.c)這兩部分,這邊就是要判斷Target內External Debug System的版本,這樣才可以呼叫對應的函式來做更進一步的初始化tt->examine(target)!
當然本文還是以0.13為主,所以,一樣,先上程式碼,讓我們看看0.13中是如何做更進一步的處理!

參考以下內容(src/target/riscv/riscv-013.c):

static int examine(struct target *target)
{
    /* Don't need to select dbus, since the first thing we do is read dtmcontrol. */

    uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
    LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
    LOG_DEBUG("  dmireset=%d", get_field(dtmcontrol, DTM_DTMCS_DMIRESET));
    LOG_DEBUG("  idle=%d", get_field(dtmcontrol, DTM_DTMCS_IDLE));
    LOG_DEBUG("  dmistat=%d", get_field(dtmcontrol, DTM_DTMCS_DMISTAT));
    LOG_DEBUG("  abits=%d", get_field(dtmcontrol, DTM_DTMCS_ABITS));
    LOG_DEBUG("  version=%d", get_field(dtmcontrol, DTM_DTMCS_VERSION));
    if (dtmcontrol == 0) {
        LOG_ERROR("dtmcontrol is 0. Check JTAG connectivity/board power.");
        return ERROR_FAIL;
    }
    if (get_field(dtmcontrol, DTM_DTMCS_VERSION) != 1) {
        LOG_ERROR("Unsupported DTM version %d. (dtmcontrol=0x%x)",
                get_field(dtmcontrol, DTM_DTMCS_VERSION), dtmcontrol);
        return ERROR_FAIL;
    }

    riscv013_info_t *info = get_info(target);
    info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
    info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);

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

    uint32_t hartinfo = dmi_read(target, DMI_HARTINFO);

    LOG_DEBUG("dmcontrol: 0x%08x", dmcontrol);
    LOG_DEBUG("dmstatus:  0x%08x", dmstatus);
    LOG_DEBUG("hartinfo:  0x%08x", hartinfo);

    info->datasize = get_field(hartinfo, DMI_HARTINFO_DATASIZE);
    info->dataaccess = get_field(hartinfo, DMI_HARTINFO_DATAACCESS);
    info->dataaddr = get_field(hartinfo, DMI_HARTINFO_DATAADDR);

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

    /* Check that abstract data registers are accessible. */
    uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
    info->datacount = get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT);
    info->progbufsize = get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE);

    /* Before doing anything else we must first enumerate the harts. */
    RISCV_INFO(r);
    r->impebreak = get_field(dmstatus, DMI_DMSTATUS_IMPEBREAK);

    /* Don't call any riscv_* functions until after we've counted the number of
     * cores and initialized registers. */
    for (int i = 0; i < RISCV_MAX_HARTS; ++i) {
        if (!riscv_rtos_enabled(target) && i != target->coreid)
            continue;

        r->current_hartid = i;
        riscv013_select_current_hart(target);

        uint32_t s = dmi_read(target, DMI_DMSTATUS);
        if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
            break;
        r->hart_count = i + 1;

        if (!riscv_is_halted(target))
            riscv013_halt_current_hart(target);

        /* Without knowing anything else we can at least mess with the
         * program buffer. */
        r->debug_buffer_size[i] = info->progbufsize;

        int result = register_read_abstract(target, NULL, GDB_REGNO_S0, 64);
        if (result == ERROR_OK)
            r->xlen[i] = 64;
        else
            r->xlen[i] = 32;

        register_read_direct(target, &r->misa, GDB_REGNO_MISA);

        /* Now init registers based on what we discovered. */
        if (riscv_init_registers(target) != ERROR_OK)
            return ERROR_FAIL;

        /* Display this as early as possible to help people who are using
         * really slow simulators. */
        LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, i, r->xlen[i],
                r->misa);
    }

    LOG_DEBUG("Enumerated %d harts", r->hart_count);

    /* Then we check the number of triggers availiable to each hart. */
    riscv_enumerate_triggers(target);

    /* Resumes all the harts, so the debugger can later pause them. */
    /* TODO: Only do this if the harts were halted to start with. */
    riscv_resume_all_harts(target);
    target->state = TARGET_RUNNING;

    target_set_examined(target);

    if (target->rtos)
        riscv_update_threads(target->rtos);

    /* Some regression suites rely on seeing 'Examined RISC-V core' to know
     * when they can connect with gdb/telnet.
     * We will need to update those suites if we want to change that text. */
    LOG_INFO("Examined RISC-V core; found %d harts",
            riscv_count_harts(target));
    for (int i = 0; i < riscv_count_harts(target); ++i) {
        if (riscv_hart_enabled(target, i)) {
            LOG_INFO(" hart %d: XLEN=%d, %d triggers", i, r->xlen[i],
                    r->trigger_count[i]);
        } else {
            LOG_INFO(" hart %d: currently disabled", i);
        }
    }
    return ERROR_OK;
}

挖靠,這函數也太長了吧!
看得我都昏了.......

一樣,底下分成幾個小節來各個擊破!
  
  

3.1 RISC-V Examine (1) - Init Debug Module

首先看到這部分:

    /* Don't need to select dbus, since the first thing we do is read dtmcontrol. */

    uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
    LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
    LOG_DEBUG("  dmireset=%d", get_field(dtmcontrol, DTM_DTMCS_DMIRESET));
    LOG_DEBUG("  idle=%d", get_field(dtmcontrol, DTM_DTMCS_IDLE));
    LOG_DEBUG("  dmistat=%d", get_field(dtmcontrol, DTM_DTMCS_DMISTAT));
    LOG_DEBUG("  abits=%d", get_field(dtmcontrol, DTM_DTMCS_ABITS));
    LOG_DEBUG("  version=%d", get_field(dtmcontrol, DTM_DTMCS_VERSION));
    if (dtmcontrol == 0) {
        LOG_ERROR("dtmcontrol is 0. Check JTAG connectivity/board power.");
        return ERROR_FAIL;
    }
    if (get_field(dtmcontrol, DTM_DTMCS_VERSION) != 1) {
        LOG_ERROR("Unsupported DTM version %d. (dtmcontrol=0x%x)",
                get_field(dtmcontrol, DTM_DTMCS_VERSION), dtmcontrol);
        return ERROR_FAIL;
    }

    riscv013_info_t *info = get_info(target);
    info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);   ///譯註: 在DMI傳輸中,需要判斷a-bits的長度!
    info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);

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

前面這部分,沒甚麼好說的,主要就是把Debug Transport Module和Debug Modue中比較重要的幾個Register 其實也才兩個 給讀出來!

  • Debug Transport Module的$dtmsc
  • Debug Module的$dmstatus

忘記的話可以參考

這邊基本上除了印出每個欄位到Log中外,另外就是設定
abits和idle這兩部分

    info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
    info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);

再來就是比較重要的部分,對整個Debug Module去做Reset:

    /* Reset the Debug Module. */
    dmi_write(target, DMI_DMCONTROL, 0);    ///譯註: Reset Debug Module就是在dmactive中寫入0
    dmi_write(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE);   ///譯註: 然後把它重啟

重啟之後,就繼續接下來初始化的部分:

    uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
    uint32_t hartinfo = dmi_read(target, DMI_HARTINFO);

    LOG_DEBUG("dmcontrol: 0x%08x", dmcontrol);
    LOG_DEBUG("dmstatus:  0x%08x", dmstatus);
    LOG_DEBUG("hartinfo:  0x%08x", hartinfo);
    
    info->datasize = get_field(hartinfo, DMI_HARTINFO_DATASIZE);
    info->dataaccess = get_field(hartinfo, DMI_HARTINFO_DATAACCESS);
    info->dataaddr = get_field(hartinfo, DMI_HARTINFO_DATAADDR);

然後判斷一下Debug Module的狀態

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

最後是判斷Debug Module支不支援Abstract Commands和Program Buffer

    /* Check that abstract data registers are accessible. */
    uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
    info->datacount = get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT);
    info->progbufsize = get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE);

    /* Before doing anything else we must first enumerate the harts. */
    RISCV_INFO(r);
    r->impebreak = get_field(dmstatus, DMI_DMSTATUS_IMPEBREAK);

忘記Abstract Commands和Program Buffer,可以回顧一下「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」!

以上就是針對Debug Module和相關資料進行初始化的部分!
  
  

3.2 RISC-V Examine (2) - Init Hart

底下實作,是針對每個Hart,去做初始化的部分:

    for (int i = 0; i < RISCV_MAX_HARTS; ++i) {
        if (!riscv_rtos_enabled(target) && i != target->coreid)
            continue;

        r->current_hartid = i;
        riscv013_select_current_hart(target);

        uint32_t s = dmi_read(target, DMI_DMSTATUS);
        if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
            break;
        r->hart_count = i + 1;

        if (!riscv_is_halted(target))
            riscv013_halt_current_hart(target);

        /* Without knowing anything else we can at least mess with the
         * program buffer. */
        r->debug_buffer_size[i] = info->progbufsize;

        int result = register_read_abstract(target, NULL, GDB_REGNO_S0, 64);
        if (result == ERROR_OK)
            r->xlen[i] = 64;
        else
            r->xlen[i] = 32;

        register_read_direct(target, &r->misa, GDB_REGNO_MISA);

        /* Now init registers based on what we discovered. */
        if (riscv_init_registers(target) != ERROR_OK)
            return ERROR_FAIL;

        /* Display this as early as possible to help people who are using
         * really slow simulators. */
        LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, i, r->xlen[i],
                r->misa);
    }

初始化的流程如下:

  • 從$dmstatus中,判斷這個Hart存不存在: if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
  • 接著利用register_read_abstract(target, NULL, GDB_REGNO_S0, 64);利用Abstract Command試著讀取64-bits的$S0,如果成功,表示該Hart為64位元的,反之就是32位元的!? (註1)
  • 然後看看這個Hart所支援的ISAregister_read_direct(target, &r->misa, GDB_REGNO_MISA);
  • 最後是初始化這個Hart的Registersriscv_init_registers(target)

** (註1) 痾! 那萬一Hart是64位元但是不支援Abstract Command的話勒!? **
  
  

3.3 RISC-V Examine (3) - Init Trigger & Resume Target

終於來到最後這部分啦!!
首先是初始化Trigger的部分:

    /* Then we check the number of triggers availiable to each hart. */
    riscv_enumerate_triggers(target);

這部分在「Day 13: 了解Trigger Module的神秘面紗(下)~~!」中有提到過,可以參考其中的"# 2.1 初始化流程"

在初始化完成後,就可以將Target進行Resume:

    /* Resumes all the harts, so the debugger can later pause them. */
    /* TODO: Only do this if the harts were halted to start with. */
    riscv_resume_all_harts(target);
    target->state = TARGET_RUNNING;

    target_set_examined(target);

等待Debugger連上後,OpenOCD會讓Target再次進入Halted的階段,
因此這邊可以將Hart進行Free-Run(Resume)!!
  
  
  

99. 結語

在這系列中,本文先暖身了一下,剖析OpenOCD中如何處理Target初始化的部分,
並利用之後的examine()來偵測必要資訊!

就如同侯捷老師所說的「源碼面前,了無秘密」!
這邊就是要把程式碼塞好、塞滿!!
  
  
  

參考資料

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

上一篇
Day 15: 讓百萬人都驚呆的Debug Transport Module~~(下)
下一篇
Day 17: 深入淺出 RISC-V 源碼剖析 (2) - Target Status Control
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言