iT邦幫忙

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

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

Day 19: 深入淺出 RISC-V 源碼剖析 (4) - Memory Access

  • 分享至 

  • xImage
  •  

0. 前言

經過上篇簡單得介紹Register Access後,今天終於要介紹一下超級大魔王Memory Access的部分!
在整個實作當中,由於Memory Access常會被用到,因此針對這邊修改的Commit也很多,
實作方式也不斷地改變!

今天將會談到兩大主題:

  • Memory Access
  • Batch Program
      
      
      

1. Memory Access (以Read Memory為例)

進入點,很簡單,就是直接呼叫013裡的read_memory()來處理,
參考以下內容(src/target/riscv/riscv.c):

static int riscv_read_memory(struct target *target, target_addr_t address,
        uint32_t size, uint32_t count, uint8_t *buffer)
{
    struct target_type *tt = get_target_type(target);
    return tt->read_memory(target, address, size, count, buffer);
}

然後就讓我們來觀賞一下壯觀的實作,請參考以下內容(src/target/riscv/riscv-013.c):

/**
 * Read the requested memory, taking care to execute every read exactly once,
 * even if cmderr=busy is encountered.
 */
static int read_memory(struct target *target, target_addr_t address,
        uint32_t size, uint32_t count, uint8_t *buffer)
{
    RISCV013_INFO(info);

    int result = ERROR_OK;

    LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count,
            size, address);

    select_dmi(target);

    /* s0 holds the next address to write to
     * s1 holds the next data value to write
     */
    uint64_t s0, s1;
    if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
        return ERROR_FAIL;
    if (register_read_direct(target, &s1, GDB_REGNO_S1) != ERROR_OK)
        return ERROR_FAIL;

    if (execute_fence(target) != ERROR_OK)
        return ERROR_FAIL;

    /* Write the program (load, increment) */
    struct riscv_program program;
    riscv_program_init(&program, target);
    switch (size) {
        case 1:
            riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 2:
            riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 4:
            riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        default:
            LOG_ERROR("Unsupported size: %d", size);
            return ERROR_FAIL;
    }
    riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);

    if (riscv_program_ebreak(&program) != ERROR_OK)
        return ERROR_FAIL;
    riscv_program_write(&program);

    /* Write address to S0, and execute buffer. */
    result = register_write_direct(target, GDB_REGNO_S0, address);
    if (result != ERROR_OK)
        goto error;
    uint32_t command = access_register_command(GDB_REGNO_S1, riscv_xlen(target),
                AC_ACCESS_REGISTER_TRANSFER |
                AC_ACCESS_REGISTER_POSTEXEC);
    result = execute_abstract_command(target, command);
    if (result != ERROR_OK)
        goto error;

    /* First read has just triggered. Result is in s1. */

    dmi_write(target, DMI_ABSTRACTAUTO,
            1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);

    /* read_addr is the next address that the hart will read from, which is the
     * value in s0. */
    riscv_addr_t read_addr = address + size;
    /* The next address that we need to receive data for. */
    riscv_addr_t receive_addr = address;
    riscv_addr_t fin_addr = address + (count * size);
    unsigned skip = 1;
    while (read_addr < fin_addr) {
        LOG_DEBUG("read_addr=0x%" PRIx64 ", receive_addr=0x%" PRIx64
                ", fin_addr=0x%" PRIx64, read_addr, receive_addr, fin_addr);
        /* The pipeline looks like this:
         * memory -> s1 -> dm_data0 -> debugger
         * It advances every time the debugger reads dmdata0.
         * So at any time the debugger has just read mem[s0 - 3*size],
         * dm_data0 contains mem[s0 - 2*size]
         * s1 contains mem[s0-size] */

        LOG_DEBUG("creating burst to read from 0x%" TARGET_PRIxADDR
                " up to 0x%" TARGET_PRIxADDR, read_addr, fin_addr);
        assert(read_addr >= address && read_addr < fin_addr);
        struct riscv_batch *batch = riscv_batch_alloc(target, 32,
                info->dmi_busy_delay + info->ac_busy_delay);

        size_t reads = 0;
        for (riscv_addr_t addr = read_addr; addr < fin_addr; addr += size) {
            riscv_batch_add_dmi_read(batch, DMI_DATA0);

            reads++;
            if (riscv_batch_full(batch))
                break;
        }

        riscv_batch_run(batch);

        /* Wait for the target to finish performing the last abstract command,
         * and update our copy of cmderr. */
        uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
        while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
            abstractcs = dmi_read(target, DMI_ABSTRACTCS);
        info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);

        unsigned cmderr = info->cmderr;
        riscv_addr_t next_read_addr;
        uint32_t dmi_data0 = -1;
        switch (info->cmderr) {
            case CMDERR_NONE:
                LOG_DEBUG("successful (partial?) memory read");
                next_read_addr = read_addr + reads * size;
                break;
            case CMDERR_BUSY:
                LOG_DEBUG("memory read resulted in busy response");
                increase_ac_busy_delay(target);
                riscv013_clear_abstract_error(target);

                dmi_write(target, DMI_ABSTRACTAUTO, 0);

                /* This is definitely a good version of the value that we
                 * attempted to read when we discovered that the target was
                 * busy. */
                dmi_data0 = dmi_read(target, DMI_DATA0);

                /* Clobbers DMI_DATA0. */
                result = register_read_direct(target, &next_read_addr,
                        GDB_REGNO_S0);
                if (result != ERROR_OK) {
                    riscv_batch_free(batch);
                    goto error;
                }
                /* Restore the command, and execute it.
                 * Now DMI_DATA0 contains the next value just as it would if no
                 * error had occurred. */
                dmi_write(target, DMI_COMMAND, command);

                dmi_write(target, DMI_ABSTRACTAUTO,
                        1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
                break;
            default:
                LOG_ERROR("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
                riscv013_clear_abstract_error(target);
                riscv_batch_free(batch);
                result = ERROR_FAIL;
                goto error;
        }

        /* Now read whatever we got out of the batch. */
        for (size_t i = 0; i < reads; i++) {
            if (read_addr >= next_read_addr)
                break;

            read_addr += size;

            if (skip > 0) {
                skip--;
                continue;
            }

            riscv_addr_t offset = receive_addr - address;
            uint64_t dmi_out = riscv_batch_get_dmi_read(batch, i);
            uint32_t value = get_field(dmi_out, DTM_DMI_DATA);
            write_to_buf(buffer + offset, value, size);
            LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%08x", receive_addr,
                    value);

            receive_addr += size;
        }
        riscv_batch_free(batch);

        if (cmderr == CMDERR_BUSY) {
            riscv_addr_t offset = receive_addr - address;
            write_to_buf(buffer + offset, dmi_data0, size);
            LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%08x", receive_addr,
                    dmi_data0);
            read_addr += size;
            receive_addr += size;
        }
    }

    dmi_write(target, DMI_ABSTRACTAUTO, 0);

    if (count > 1) {
        /* Read the penultimate word. */
        uint64_t value = dmi_read(target, DMI_DATA0);
        write_to_buf(buffer + receive_addr - address, value, size);
        LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%" PRIx64, receive_addr, value);
        receive_addr += size;
    }

    /* Read the last word. */
    uint64_t value;
    result = register_read_direct(target, &value, GDB_REGNO_S1);
    if (result != ERROR_OK)
        goto error;
    write_to_buf(buffer + receive_addr - address, value, size);
    LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%" PRIx64, receive_addr, value);
    receive_addr += size;

    riscv_set_register(target, GDB_REGNO_S0, s0);
    riscv_set_register(target, GDB_REGNO_S1, s1);
    return ERROR_OK;

error:
    dmi_write(target, DMI_ABSTRACTAUTO, 0);

    riscv_set_register(target, GDB_REGNO_S0, s0);
    riscv_set_register(target, GDB_REGNO_S1, s1);
    return result;
}

娘子! 快跟牛魔王出來看上帝!!
一個Memory Read竟然這麼落落長........!

不過基本上我們可以把重點流程分解如下:

  1. 備份$S0和$S1
  2. 執行fence指令
  3. 準備好Program Buffer的內容
  4. 將目標的Address寫入$S0中
  5. 執行第一次Program Buffer,並將第一次的結果讀入$S1中
  6. 設定Debug Module中的$abstractauto
  7. 建立DMI Read $Data0的Batch Commands
  8. 執行Batch Commands
  9. 等待執行完畢,並判斷是否成功
  10. 從Batch中讀回資料
  11. 重複6.~10.,直到所有資料皆讀取完畢!
  12. 恢復$S0和$S1

大概就是這12個動作!!
除了Batch Commands會留待下面的章節另外剖析外,其餘將會一一說明!!

讓我們開始吧!!!

首先是Step 1: 備份$S0和$S1,這個簡單:

    /* s0 holds the next address to write to
     * s1 holds the next data value to write
     */
    uint64_t s0, s1;
    if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
        return ERROR_FAIL;
    if (register_read_direct(target, &s1, GDB_REGNO_S1) != ERROR_OK)
        return ERROR_FAIL;

再來是Step 2. 執行fence指令,這也還好:

    if (execute_fence(target) != ERROR_OK)
        return ERROR_FAIL;

Step 3. 準備好Program Buffer的內容,基本上內容如下:

lw    $S1, 0($S0)
addi  $S0, $S0, 4
ebreak

很簡單的三道指令,基本概念如下:

  • $S0存放目前目標的Address
  • 將$S0指向Address的記憶體的值存入$S1中
  • 最後將$S0+4指向下一個目標

實作的方式也很簡單:

    /* Write the program (load, increment) */
    struct riscv_program program;
    riscv_program_init(&program, target);
    switch (size) {
        case 1:
            riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 2:
            riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 4:
            riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        default:
            LOG_ERROR("Unsupported size: %d", size);
            return ERROR_FAIL;
    }
    riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);

    if (riscv_program_ebreak(&program) != ERROR_OK)
        return ERROR_FAIL;
    riscv_program_write(&program);      ///譯註: 這時候先把Program Buffer的內容寫入,但還沒有開始執行!!

再過來是Step 4. 將目標的Address寫入$S0中,這也很簡單,
簡單利用「Day 18: 深入淺出 RISC-V 源碼剖析 (3) - Register Access」介紹的方法即可:

    result = register_write_direct(target, GDB_REGNO_S0, address);
    if (result != ERROR_OK)
        goto error;

在過來Step 5. 執行第一次Program Buffer,並將第一次的結果讀入$S1中

    uint32_t command = access_register_command(GDB_REGNO_S1, riscv_xlen(target),
                AC_ACCESS_REGISTER_TRANSFER |
                AC_ACCESS_REGISTER_POSTEXEC);
    result = execute_abstract_command(target, command);
    if (result != ERROR_OK)
        goto error;

還記得「Day 10: RISC-V Debug Module (下篇)-Debug Module Registers」中的這張圖嗎:

https://ithelp.ithome.com.tw/upload/images/20180106/2010732700X9i3hRX5.jpg
---引用自RISC-V External Debug Support 0.13

不過今天我們只看紅色括號起的地方!
當Abstrace Command中"transfer"和"postexec"同時被拉起來並執行的時候,
隱藏了一個小小的觀念在裡面:
先做Register Access之後,才會去執行Program Buffer!!!
請記住這個小小的地方,他對於接下來的Memory Access的設計有關!

不過這邊,我們其實只是要第一次執行Program Buffer而已,
所以目前$S1裡面存的值就是Mem[$S0]

再過來是Step 6. 設定Debug Module中的$abstractauto!

這邊先介紹一下$abstractauto這個Register:

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

Debug Module中0x18 abstractauto,這功能是硬體可選支援的,
在這版本的實作中,皆假設這個支援存在,主要欄位有兩個:

  • autoexecprogbuf: 假如對Program Buffer有任何讀寫,都會自動執行Abstract Command
  • autoexecdata: 假如對$Data0有任何讀寫,都會自動執行Abstract Command

我們在這邊主要是需要autoexecdata的功能,
當我們在讀取Debug Module中的$Data0的時候,同時硬體會自動執行一次Abstract Command!

然後讓我們繼續看下去!
再過來就是一個迴圈的部分:

    /* read_addr is the next address that the hart will read from, which is the
     * value in s0. */
    riscv_addr_t read_addr = address + size;
    /* The next address that we need to receive data for. */
    riscv_addr_t receive_addr = address;
    riscv_addr_t fin_addr = address + (count * size);
    unsigned skip = 1;
    while (read_addr < fin_addr) {

        ///Step 7. 建立DMI Read $Data0的Batch Commands
        

        ///Step 8. 執行Batch Commands


        ///Step 9. 等待執行完畢,並判斷是否成功


        ///Step 10. 從Batch中讀回資料
    }

基本架構就是重複執行上述7~10的過程,直到讀完所需要的長度為止!
當然如果你只需要讀一道Memory(32bits)的話,那就不會進入到這個迴圈當中了!

================================= 我是分隔線 =================================

接下來介紹一下本篇最重要的部分,深入剖析Memory Access的原理!

試想一個狀況,假如我今天希望能夠讀取完一到32bits的Memory之後,
讓硬體自動幫我更新資料,讀取下一位置的Memory,我應該怎麼做!!?

我有的資源如下:

  1. 一個Program Buffer,裡面會做兩件事:
  • 存Mem[$S0]到$S1中
  • 更新$S0 = $S0 + 4
  1. 一個Abstract Command: 自動將$S1的內容存到$Data0後,自動執行一次Program Buffer
  2. 一個autoexecdata的機制,讓我在讀取$Data0後,自動執行Abstract Command一次

讀到這邊,讓讀者想一下,這邊應該要怎麼設計!?

================================= 我是分隔線 =================================

來公布答案吧!
就是一直不段的讀取$Data0,讓autoexecdata的機制自動執行Abstract Command一次,
在執行Abstract Command的時候,會先將$S1的內容存到$Data0後,再去執行Program Buffer一次,讓Program Buffer裡面的指令,將存Mem[$S0]到$S1中,並自動更新$S0到下一個位置!!

有沒有突然覺得這個機制設計得很神啊!!!!!!!!!!!!!
一起加入Debugger的世界吧!

整個Pipeline看起來如下:

$S1 DM中 $Data0 Debugger 讀取到的值 說明
Mem[0] X X 剛開始執行batch之前
Mem[4] Mem[0] X DMI Read 1
Mem[8] Mem[4] Mem[0] DMI Read 2
Mem[12] Mem[8] Mem[4] DMI Read 3
Mem[16] Mem[12] Mem[8] DMI Read 4
... Mem[16] Mem[12] DMI Read 5
... ... Mem[16] DMI Read 6
... ... ... ...

大概就是程式碼中註解那部分的意思:

        /* The pipeline looks like this:
         * memory -> s1 -> dm_data0 -> debugger
         * It advances every time the debugger reads dmdata0.
         * So at any time the debugger has just read mem[s0 - 3*size],
         * dm_data0 contains mem[s0 - 2*size]
         * s1 contains mem[s0-size] */

基本的概念大概就是這樣!!!

所以Step 7. 建立DMI Read $Data0的Batch Commands和Step 8. 執行Batch Commands的實作如下:

        ///譯註: 先建立一個Batch Commands存放的空間
        struct riscv_batch *batch = riscv_batch_alloc(target, 32,
                info->dmi_busy_delay + info->ac_busy_delay);

        size_t reads = 0;
        for (riscv_addr_t addr = read_addr; addr < fin_addr; addr += size) {
            ///譯註: 寫入讀取DM中$Data0的指令,直到空間滿了為止!
            riscv_batch_add_dmi_read(batch, DMI_DATA0);

            reads++;
            if (riscv_batch_full(batch))
                break;
        }

        ///譯註: 執行Batch Commands
        riscv_batch_run(batch);

然後記得要去檢查Abstract Command執行的狀況!
Step 9. 等待執行完畢,並判斷是否成功:

        unsigned cmderr = info->cmderr;
        riscv_addr_t next_read_addr;
        uint32_t dmi_data0 = -1;
        switch (info->cmderr) {
            case CMDERR_NONE:
                LOG_DEBUG("successful (partial?) memory read");
                next_read_addr = read_addr + reads * size;
                break;
            

            case CMDERR_BUSY:
                中間略過...
            

            default:
                LOG_ERROR("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
                riscv013_clear_abstract_error(target);
                riscv_batch_free(batch);
                result = ERROR_FAIL;
                goto error;
        }

最後是Step 10. 從Batch中讀回資料:

        /* Now read whatever we got out of the batch. */
        for (size_t i = 0; i < reads; i++) {
            if (read_addr >= next_read_addr)
                break;

            read_addr += size;

            if (skip > 0) {
                skip--;
                continue;
            }

            riscv_addr_t offset = receive_addr - address;
            uint64_t dmi_out = riscv_batch_get_dmi_read(batch, i);
            uint32_t value = get_field(dmi_out, DTM_DMI_DATA);
            write_to_buf(buffer + offset, value, size);
            LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%08x", receive_addr,
                    value);

            receive_addr += size;
        }

        ///譯註: 清空Batch
        riscv_batch_free(batch);

到這邊基本上Memory Access的概念都已經剖析完畢!
至於Write Memory的部分也是類似啦!
直接放在附錄的部分給大家參考!
  
  
  

2. Batch Commands

好經過上面的剖析之後,只剩下一個重點還沒有說明!?
就是什麼是Batch Commands啦!?
說他是整個Memory Access的核心也不為過!

基本的概念如下:

  1. 首先對DTM寫入第一道命令,比方說Memory Acceess的話就是DMI_READ_$DATA0
  2. DTM等待處理完畢
  3. 再寫入一道NOP到DTM中,把之前的處理好的結果給"擠"出DTM!
  4. 重複上述流程,直到所有的DMI_READ_$DATA0都處理完畢

所以假設field[0].out裡面放的是DMI_READ1(第一次Read DMI $Data0),等到寫入field[1].out = NOP的時候,同時field[1].in就會是field[0].out處理後的結果!

別忘記了在OpenOCD JTAG設計中,out指的是從OpenOCD到達(out)Target端,in指的是OpenOCD從Target端得到(in)的值

整個Pipeline大概長這樣:

Debugger寫入(out)的值 DTM中的$dmi Debugger得到(in)的值
field[0].out = DMI_READ1 X X
field[1].out = NOP field[1].in = DMI_READ 1
field[2].out = DMI_READ2 X X
field[3].out = NOP field[1].in = DMI_READ2
... ... ...
必須承認真的不太會畫圖

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

2.1 Batch DMI Read

先看程式碼的部分,請參考以下內容(src/target/riscv/riscv-013.c):

size_t riscv_batch_add_dmi_read(struct riscv_batch *batch, unsigned address)
{
    assert(batch->used_scans < batch->allocated_scans);
    struct scan_field *field = batch->fields + batch->used_scans;
    field->num_bits = riscv_dmi_write_u64_bits(batch->target);
    field->out_value = (void *)(batch->data_out + batch->used_scans * sizeof(uint64_t));
    field->in_value  = (void *)(batch->data_in  + batch->used_scans * sizeof(uint64_t));
    riscv_fill_dmi_read_u64(batch->target, (char *)field->out_value, address);
    riscv_fill_dmi_nop_u64(batch->target, (char *)field->in_value);
    batch->last_scan = RISCV_SCAN_TYPE_READ;
    batch->used_scans++;

    /* FIXME We get the read response back on the next scan.  For now I'm
     * just sticking a NOP in there, but this should be coelesced away. */
    ///譯註: 加個NOP在後方!
    riscv_batch_add_nop(batch);

    ///譯註: 這邊只是設定一下這個filed出去後的結果,會在下面一道NOP的in_value中!
    batch->read_keys[batch->read_keys_used] = batch->used_scans - 1;
    LOG_DEBUG("read key %u for batch 0x%p is %u (0x%p)",
            (unsigned) batch->read_keys_used, batch, (unsigned) (batch->used_scans - 1),
            batch->data_in + sizeof(uint64_t) * (batch->used_scans + 1));
    return batch->read_keys_used++;
}

基本上就是把要送到Target端的資料(out_value)給放好,至於in_value的部分就不是很重要了,可以直接放個NOP

然後比較重要的是在下一道的地方給放上一個NOP:

riscv_batch_add_nop(batch);

幾本上riscv_batch_add_nop()做的事情也差不多,請參考以下內容(src/target/riscv/riscv-013.c):


void riscv_batch_add_nop(struct riscv_batch *batch)
{
    assert(batch->used_scans < batch->allocated_scans);
    struct scan_field *field = batch->fields + batch->used_scans;
    field->num_bits = riscv_dmi_write_u64_bits(batch->target);
    field->out_value = (void *)(batch->data_out + batch->used_scans * sizeof(uint64_t));
    field->in_value  = (void *)(batch->data_in  + batch->used_scans * sizeof(uint64_t));
    riscv_fill_dmi_nop_u64(batch->target, (char *)field->out_value);
    riscv_fill_dmi_nop_u64(batch->target, (char *)field->in_value);
    batch->last_scan = RISCV_SCAN_TYPE_NOP;
    batch->used_scans++;
    LOG_DEBUG("  added NOP with in_value=0x%p", field->in_value);
}

就是在送給Target端的資料裡面放上NOP!
  
  

2.2 Batch Read Result

基本上就是把對應欄位的資料給讀出來,請參考以下內容(src/target/riscv/riscv-013.c):

uint64_t riscv_batch_get_dmi_read(struct riscv_batch *batch, size_t key)
{
    assert(key < batch->read_keys_used);
    size_t index = batch->read_keys[key];
    assert(index <= batch->used_scans);
    uint8_t *base = batch->data_in + 8 * index;
    return base[0] |
        ((uint64_t) base[1]) << 8 |
        ((uint64_t) base[2]) << 16 |
        ((uint64_t) base[3]) << 24 |
        ((uint64_t) base[4]) << 32 |
        ((uint64_t) base[5]) << 40 |
        ((uint64_t) base[6]) << 48 |
        ((uint64_t) base[7]) << 56;
}

  
  
  

99. 結語

從發想到設計出來,真心覺得要對JTAG運作非常熟悉的人才可以設計出來的方式!
佩服佩服!!

今天一樣是把程式碼塞好、塞滿!
  
  
  

Z. 附錄 - Write Memory

單純放在這邊 程式碼塞好塞滿!
請參考以下內容(src/target/riscv/riscv-013.c):

static int write_memory(struct target *target, target_addr_t address,
        uint32_t size, uint32_t count, const uint8_t *buffer)
{
    RISCV013_INFO(info);

    LOG_DEBUG("writing %d words of %d bytes to 0x%08lx", count, size, (long)address);

    select_dmi(target);

    /* s0 holds the next address to write to
     * s1 holds the next data value to write
     */

    int result = ERROR_OK;
    uint64_t s0, s1;
    if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
        return ERROR_FAIL;
    if (register_read_direct(target, &s1, GDB_REGNO_S1) != ERROR_OK)
        return ERROR_FAIL;

    /* Write the program (store, increment) */
    struct riscv_program program;
    riscv_program_init(&program, target);

    switch (size) {
        case 1:
            riscv_program_sbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 2:
            riscv_program_shr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        case 4:
            riscv_program_swr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
            break;
        default:
            LOG_ERROR("Unsupported size: %d", size);
            result = ERROR_FAIL;
            goto error;
    }

    riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);

    result = riscv_program_ebreak(&program);
    if (result != ERROR_OK)
        goto error;
    riscv_program_write(&program);

    riscv_addr_t cur_addr = address;
    riscv_addr_t fin_addr = address + (count * size);
    bool setup_needed = true;
    LOG_DEBUG("writing until final address 0x%016" PRIx64, fin_addr);
    while (cur_addr < fin_addr) {
        LOG_DEBUG("transferring burst starting at address 0x%016" PRIx64,
                cur_addr);

        struct riscv_batch *batch = riscv_batch_alloc(
                target,
                32,
                info->dmi_busy_delay + info->ac_busy_delay);

        /* To write another word, we put it in S1 and execute the program. */
        unsigned start = (cur_addr - address) / size;
        for (unsigned i = start; i < count; ++i) {
            unsigned offset = size*i;
            const uint8_t *t_buffer = buffer + offset;

            uint32_t value;
            switch (size) {
                case 1:
                    value = t_buffer[0];
                    break;
                case 2:
                    value = t_buffer[0]
                        | ((uint32_t) t_buffer[1] << 8);
                    break;
                case 4:
                    value = t_buffer[0]
                        | ((uint32_t) t_buffer[1] << 8)
                        | ((uint32_t) t_buffer[2] << 16)
                        | ((uint32_t) t_buffer[3] << 24);
                    break;
                default:
                    LOG_ERROR("unsupported access size: %d", size);
                    riscv_batch_free(batch);
                    result = ERROR_FAIL;
                    goto error;
            }

            LOG_DEBUG("M[0x%08" PRIx64 "] writes 0x%08x", address + offset, value);
            cur_addr += size;

            if (setup_needed) {
                result = register_write_direct(target, GDB_REGNO_S0,
                        address + offset);
                if (result != ERROR_OK) {
                    riscv_batch_free(batch);
                    goto error;
                }

                /* Write value. */
                dmi_write(target, DMI_DATA0, value);

                /* Write and execute command that moves value into S1 and
                 * executes program buffer. */
                uint32_t command = access_register_command(GDB_REGNO_S1, 32,
                        AC_ACCESS_REGISTER_POSTEXEC |
                        AC_ACCESS_REGISTER_TRANSFER |
                        AC_ACCESS_REGISTER_WRITE);
                result = execute_abstract_command(target, command);
                if (result != ERROR_OK) {
                    riscv_batch_free(batch);
                    goto error;
                }

                /* Turn on autoexec */
                dmi_write(target, DMI_ABSTRACTAUTO,
                        1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);

                setup_needed = false;
            } else {
                riscv_batch_add_dmi_write(batch, DMI_DATA0, value);
                if (riscv_batch_full(batch))
                    break;
            }
        }

        result = riscv_batch_run(batch);
        riscv_batch_free(batch);
        if (result != ERROR_OK)
            goto error;

        /* Note that if the scan resulted in a Busy DMI response, it
         * is this read to abstractcs that will cause the dmi_busy_delay
         * to be incremented if necessary. */

        uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
        while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
            abstractcs = dmi_read(target, DMI_ABSTRACTCS);
        info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
        switch (info->cmderr) {
            case CMDERR_NONE:
                LOG_DEBUG("successful (partial?) memory write");
                break;
            case CMDERR_BUSY:
                LOG_DEBUG("memory write resulted in busy response");
                riscv013_clear_abstract_error(target);
                increase_ac_busy_delay(target);

                dmi_write(target, DMI_ABSTRACTAUTO, 0);
                result = register_read_direct(target, &cur_addr, GDB_REGNO_S0);
                if (result != ERROR_OK)
                    goto error;
                setup_needed = true;
                break;

            default:
                LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
                riscv013_clear_abstract_error(target);
                result = ERROR_FAIL;
                goto error;
        }
    }

error:
    dmi_write(target, DMI_ABSTRACTAUTO, 0);

    if (register_write_direct(target, GDB_REGNO_S1, s1) != ERROR_OK)
        return ERROR_FAIL;
    if (register_write_direct(target, GDB_REGNO_S0, s0) != ERROR_OK)
        return ERROR_FAIL;

    if (execute_fence(target) != ERROR_OK)
        return ERROR_FAIL;

    return result;
}

  
  
  

參考資料

  1. RISC-V External Debug Support 0.13
  2. GitHub: riscv/riscv-openocd
  3. Day 10: RISC-V Debug Module (下篇)-Debug Module Registers
  4. Day 18: 深入淺出 RISC-V 源碼剖析 (3) - Register Access
  5. 小蘿蔔工作室 Little Robot Studio - JTAG

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

尚未有邦友留言

立即登入留言