iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 15
0

0. 前言

經過上篇「Day 14: 讓百萬人都驚呆的Debug Transport Module~~(上)」的基本介紹後,
今天來看幾個實作的方式,看看如何得到IDCODE和對Debug Module作基本的讀寫!!
  
  
  

1. 讀取IDCODE

請參考以下程式碼

static int idcode_scan(struct target *target)
{
        struct scan_field field;
        uint8_t in_value[4];


        jtag_add_ir_scan(target->tap, &select_idcode, TAP_IDLE);    ///譯註: 基本上就是把0x01 IDCODE敲進去DTM中!!
        field.num_bits = 32;
        field.out_value = NULL;
        field.in_value = in_value;
        jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);         ///譯註: 然後從DR中把值撈回來!

        int retval = jtag_execute_queue();
        if (retval != ERROR_OK) {
                LOG_ERROR("failed jtag scan: %d", retval);
                return retval;
        }

        /* Always return to dbus. */
        jtag_add_ir_scan(target->tap, &select_dbus, TAP_IDLE);
        uint32_t in = buf_get_u32(.fieldin_value, 0, 32);
        LOG_DEBUG("IDCODE: 0x0 -> 0x%x", in);

        return in;
}

在OpenOCD中提供一個簡單的APIs用來控制JTAG的運作,如這個實作中所用到的jtag_add_ir_scan!

原型如下:

void jtag_add_ir_scan(struct jtag_tap *active, 
    struct scan_field *in_fields, 
    tap_state_t state)

簡單說明一下參數的意義:

  • active: 就是指向目前所使用到的TAP
  • in_fields: 放入要傳輸的資料 (*註1)
  • tap_state_t: 最後回到的State,以這範例來說,就是回到RUN-TEST/IDLE

** 忘記JTAG的操作可以參考這邊: 小蘿蔔工作室 Little Robot Studio - JTAG **

(*註1): 所要傳輸的資料

uint8_t ir_idcode[1] = {0x1};
struct scan_field select_idcode = {
    .in_value = NULL,
    .out_value = ir_idcode
};

  
  
  

2. DMI Operation

還記得在「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」中,我們提到的幾個用來針對DMI Access操作(Read/Write)的實作嗎!?
讓我們在這邊再複習一下!

主要分成以下三個部分:

  • DMI Read Operation
  • DMI Write Operation
  • DMI Scan(Exec)

不過呢!? 上層對於Debug Module的操作(Read/Write)我們已經提到過!
今天讓我們更深入研究一下底層(DMI Scan),DMI是如何被傳輸的!

以下程式碼請參考src/target/riscv/riscv-013.c

/**
 * exec: If this is set, assume the scan results in an execution, so more
 * run-test/idle cycles may be required.
 */
static dmi_status_t dmi_scan(struct target *target, uint16_t *address_in,
        uint64_t *data_in, dmi_op_t op, uint16_t address_out, uint64_t data_out,
        bool exec)
{
    riscv013_info_t *info = get_info(target);
    uint8_t in[8] = {0};
    uint8_t out[8];
    struct scan_field field = {
        .num_bits = info->abits + DTM_DMI_OP_LENGTH + DTM_DMI_DATA_LENGTH,
        .out_value = out,
        .in_value = in
    };

    assert(info->abits != 0);

    ///譯註: 0x11 $dmi的格式由address+data+op組成 (*註1)
    buf_set_u64(out, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, op);
    buf_set_u64(out, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, data_out);
    buf_set_u64(out, DTM_DMI_ADDRESS_OFFSET, info->abits, address_out);

    /* Assume dbus is already selected. */
    jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);

    int idle_count = info->dmi_busy_delay;
    if (exec)
        idle_count += info->ac_busy_delay;

    if (idle_count) {
        ///譯註: 加上Delay (*註2)
        jtag_add_runtest(idle_count, TAP_IDLE);
    }

    int retval = jtag_execute_queue();
    if (retval != ERROR_OK) {
        LOG_ERROR("dmi_scan failed jtag scan");
        return DMI_STATUS_FAILED;
    }

    if (data_in) {
        *data_in = buf_get_u64(in, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH);
    }

    if (address_in) {
        *address_in = buf_get_u32(in, DTM_DMI_ADDRESS_OFFSET, info->abits);
    }

    dump_field(&field);

    return buf_get_u32(in, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
}

(*註1) 可以參考「Day 14: 讓百萬人都驚呆的Debug Transport Module~~(上)」中的"# 2.3 0x11 dmi: Debug Module Interface Access"

(*註2) 可以參考「Day 14: 讓百萬人都驚呆的Debug Transport Module~~(上)」中的"# 2.2 0x10 dtmsc: DTM Control and Status"中idle欄位的建議值來設定,不過OpenOCD這邊用方式比較特別一些,
當上次DMI操作發生Busy時,他內部會有一個"線性方程式"會不斷地增加這個參考值,
然後下次遇到相關DMI的操作時,就直接拿個這算好的參考值來用!

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

static void increase_dmi_busy_delay(struct target *target)
{
    riscv013_info_t *info = get_info(target);
    info->dmi_busy_delay += info->dmi_busy_delay / 10 + 1;
    LOG_INFO("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
            info->dtmcontrol_idle, info->dmi_busy_delay,
            info->ac_busy_delay);

    dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
}

static void increase_ac_busy_delay(struct target *target)
{
    riscv013_info_t *info = get_info(target);
    info->ac_busy_delay += info->ac_busy_delay / 10 + 1;
    LOG_INFO("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
            info->dtmcontrol_idle, info->dmi_busy_delay,
            info->ac_busy_delay);
}

  
  
  

3. DTM Control and Status $dtmcs

終於來到作後啦,相信冰雪聰明的讀者看到前面IDCODE和DMI的兩個範例後,
應該知道該如何實作這部分了吧XD!?

一樣先看程式碼(改寫自src/target/riscv/riscv-013.c)

static uint32_t dtmcs_scan(struct target *target, uint32_t out)
{
    struct scan_field field;
    uint8_t in_value[4];
    uint8_t out_value[4];

    buf_set_u32(out_value, 0, 32, out);

    ///譯註: 基本上就是把0x10 dtmcs敲進去DTM中 (*註1)!!
    jtag_add_ir_scan(target->tap, &select_dtmcs, TAP_IDLE);

    field.num_bits = 32;
    field.out_value = out_value;
    field.in_value = in_value;
    jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);

    /* Always return to dmi. */
    select_dmi(target);

    int retval = jtag_execute_queue();
    if (retval != ERROR_OK) {
        LOG_ERROR("failed jtag scan: %d", retval);
        return retval;
    }

    uint32_t in = buf_get_u32(field.in_value, 0, 32);
    LOG_DEBUG("DTMCS: 0x%x -> 0x%x", out, in);

    return in;
}

(*註1): 所要傳輸的資料如下:

#define DTMCS                  0x10
uint8_t ir_dtmcs[1] = {DTMCS};
struct scan_field select_dtmcs = {
    .in_value = NULL,
    .out_value = ir_dtmcs
};

  
  
  

99. 結語

以上這幾天的文章中,終於將RISC-V底層的架構從上端Debug Module、中間Trigger Module到最後底層Debug Transport Module都給仔細地講完一遍~~
然後鐵人賽就結束了 慘

沒啦,才剛過一半而已(Day 15),明天開始將進入到全新的領域--FTDI Based Adapter,
講述從PC端到Target端中間的Debug Trasnport Hardware的相關知識及應用 (大概吧!
  
  
  

參考資料

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

上一篇
Day 14: 讓百萬人都驚呆的Debug Transport Module~~(上)
下一篇
Day 16: 深入淺出 RISC-V 源碼剖析 (1) - Overview & Init
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30

尚未有邦友留言

立即登入留言