iT邦幫忙

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

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

Day 18: 深入淺出 RISC-V 源碼剖析 (3) - Register Access

  • 分享至 

  • twitterImage
  •  

0. 前言

挖~! 終於來到第三篇啦!!!
2018年最值得高興的事就是把公司的桌電升級成SSD了~!!!
速度果然飛快啊!!

不過呢,一樣,今天還是來繼續源碼剖析一下!!
把程式碼塞好、塞滿!
來看看RISC-V中Register Access的部分!
  
  
  

1. Register Accesss

還記的在「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」中有提到Debug System中讀取Register的方式分成兩種:

  • Program Buffer
  • Abstract Command - Access Register

那篇其實已經有解釋這一部分了,不過這邊會講得更仔細些!
  
  
  

2 Read Registers

  
  

2.1 Read Registers by Abstract Command

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

static int register_get(struct reg *reg)
{
    struct target *target = (struct target *) reg->arch_info;
    uint64_t value = riscv_get_register(target, reg->number);
    buf_set_u64(reg->value, 0, reg->size, value);
    return ERROR_OK;
}


riscv_reg_t riscv_get_register(struct target *target, enum gdb_regno r)
{
    return riscv_get_register_on_hart(target, riscv_current_hartid(target), r);
}


uint64_t riscv_get_register_on_hart(struct target *target, int hartid, enum gdb_regno regid)
{
    RISCV_INFO(r);
    uint64_t value = r->get_register(target, hartid, regid);       ///譯註: 進入0.13中處理!
    LOG_DEBUG("[%d] %s: %" PRIx64, hartid, gdb_regno_name(regid), value);
    return value;
}

其中我們只關心r->get_register()是如何實作的!
完全搞不懂為啥要設計這麼多層呼叫

一樣來關心一下get_register(),
參考以下內容(src/target/riscv/riscv-013.c):

static riscv_reg_t riscv013_get_register(struct target *target, int hid, int rid)
{
    LOG_DEBUG("reading register %s on hart %d", gdb_regno_name(rid), hid);

    riscv_set_current_hartid(target, hid);

    uint64_t out;
    riscv013_info_t *info = get_info(target);

    if (rid <= GDB_REGNO_XPR31) {                           ///譯註: 普通的GPR
        register_read_direct(target, &out, rid);
    } else if (rid == GDB_REGNO_PC) {                       ///譯註: 這邊要改讀$dpc
        register_read_direct(target, &out, GDB_REGNO_DPC);
        LOG_DEBUG("read PC from DPC: 0x%016" PRIx64, out);
    } else if (rid == GDB_REGNO_PRIV) {
        uint64_t dcsr;
        register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
        buf_set_u64((unsigned char *)&out, 0, 8, get_field(dcsr, CSR_DCSR_PRV));
    } else {
        int result = register_read_direct(target, &out, rid);   ///譯註: 其他CSR
        if (result != ERROR_OK) {
            LOG_ERROR("Unable to read register %d", rid);
            out = -1;
        }

        if (rid == GDB_REGNO_MSTATUS)
            info->mstatus_actual = out;
    }

    return out;
}

這個函式主要負責分辨目前讀取Register的種類,並做出對應的處理!
比如說: 如果Debugger需要讀取$PC,在這個時候,就要改讀$DPC(進入Debug Mode時候的$PC)!

忘記$DPC可以參考「Day 11: RISC-V Debug Introduction」中"5.2 0x7b1 dpc: Debug PC"!

至於普通GPR或是其他的CSR,則呼叫register_read_direct()來處理!
一樣,來看看怎麼實作的!

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

/** Actually read registers from the target right now. */
static int register_read_direct(struct target *target, uint64_t *value, uint32_t number)
{
    int result = register_read_abstract(target, value, number,
            riscv_xlen(target));

    if (result != ERROR_OK) {
        assert(number != GDB_REGNO_S0);

        result = ERROR_OK;

        struct riscv_program program;
        riscv_program_init(&program, target);

        scratch_mem_t scratch;
        bool use_scratch = false;

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

        /* Write program to move data into s0. */

        if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
            /* TODO: Possibly set F in mstatus. */
            if (riscv_supports_extension(target, 'D') && riscv_xlen(target) < 64) {
                /* There are no instructions to move all the bits from a
                 * register, so we need to use some scratch RAM. */
                riscv_program_insert(&program, fsd(number - GDB_REGNO_FPR0, S0,
                            0));

                if (scratch_find(target, &scratch, &program, 8) != ERROR_OK)
                    return ERROR_FAIL;
                use_scratch = true;

                if (register_write_direct(target, GDB_REGNO_S0,
                            scratch.hart_address) != ERROR_OK)
                    return ERROR_FAIL;
            } else if (riscv_supports_extension(target, 'D')) {
                riscv_program_insert(&program, fmv_x_d(S0, number - GDB_REGNO_FPR0));
            } else {
                riscv_program_insert(&program, fmv_x_w(S0, number - GDB_REGNO_FPR0));
            }
        } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
            riscv_program_csrr(&program, S0, number);
        } else {
            LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
            return ERROR_FAIL;
        }

        /* Execute program. */
        result = riscv_program_exec(&program, target);

        if (use_scratch) {
            if (scratch_read64(target, &scratch, value) != ERROR_OK)
                return ERROR_FAIL;
        } else {
            /* Read S0 */
            if (register_read_direct(target, value, GDB_REGNO_S0) != ERROR_OK)
                return ERROR_FAIL;
        }

        /* Restore S0. */
        if (register_write_direct(target, GDB_REGNO_S0, s0) != ERROR_OK)
            return ERROR_FAIL;
    }

    if (result == ERROR_OK) {
        LOG_DEBUG("[%d] reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
                number, *value);
    }

    return result;
}

首先是這部分

    int result = register_read_abstract(target, value, number,
            riscv_xlen(target));

這邊主要先是試這用"Abstract Command - Access Register"的方式來讀取,
實作方式如下,參考以下內容(src/target/riscv/riscv-013.c):

static int register_read_abstract(struct target *target, uint64_t *value,
        uint32_t number, unsigned size)
{
    RISCV013_INFO(info);

    if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
            !info->abstract_read_fpr_supported)
        return ERROR_FAIL;
    if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
            !info->abstract_read_csr_supported)
        return ERROR_FAIL;

    ///譯註: Abstract Command建立!
    uint32_t command = access_register_command(number, size,
            AC_ACCESS_REGISTER_TRANSFER);

    int result = execute_abstract_command(target, command);
    if (result != ERROR_OK) {
        if (info->cmderr == CMDERR_NOT_SUPPORTED) {
            if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
                info->abstract_read_fpr_supported = false;
                LOG_INFO("Disabling abstract command reads from FPRs.");
            } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
                info->abstract_read_csr_supported = false;
                LOG_INFO("Disabling abstract command reads from CSRs.");
            }
        }
        return result;
    }

    if (value)
        *value = read_abstract_arg(target, 0);

    return ERROR_OK;
}

目前的"Abstract Command"並不支援FPR(Floating Point Register)和其他CSR的讀取!
如果是一般GPR的部分,則利用以下Command來處理!

uint32_t command = access_register_command(number, size,
            AC_ACCESS_REGISTER_TRANSFER);

主要是把目標放到Debug Module中的$data0中!!!
忘記這個Command的格式的話,可以參考「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」中"# 2.2 Access Register"相關說明!

最後利用read_abstract_arg()從$DATA0將結果讀取回來!
相關實作如下,參考以下內容(src/target/riscv/riscv-013.c):

static riscv_reg_t read_abstract_arg(struct target *target, unsigned index)
{
    riscv_reg_t value = 0;
    unsigned xlen = riscv_xlen(target);
    unsigned offset = index * xlen / 32;
    switch (xlen) {
        default:
            LOG_ERROR("Unsupported xlen: %d", xlen);
            return ~0;
        case 64:
            value |= ((uint64_t) dmi_read(target, DMI_DATA0 + offset + 1)) << 32;
        case 32:
            value |= dmi_read(target, DMI_DATA0 + offset);
    }
    return value;
}

  
  

2.1 Read Registers by Program Buffer

回到原本的register_read_direct()中,假如"Abstract Command"執行失敗的話!
就只好使用原本Program Buffer的方式!
在這邊要注意的是,因為會需要用到$s0,所以需要先把它備份起來:

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

然後你就會發現,進入了"先有雞、還是先有蛋"的問題中了....
所以從這邊可以看出,目前這版的OpenOCD的GPR"一定"都需要有"Abstract Command"的支援才行!

然後中間是建立Program Buffer的內容

        /* Write program to move data into s0. */

        if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
            /* TODO: Possibly set F in mstatus. */
            if (riscv_supports_extension(target, 'D') && riscv_xlen(target) < 64) {
                /* There are no instructions to move all the bits from a
                 * register, so we need to use some scratch RAM. */
                riscv_program_insert(&program, fsd(number - GDB_REGNO_FPR0, S0,
                            0));

                if (scratch_find(target, &scratch, &program, 8) != ERROR_OK)
                    return ERROR_FAIL;
                use_scratch = true;

                if (register_write_direct(target, GDB_REGNO_S0,
                            scratch.hart_address) != ERROR_OK)
                    return ERROR_FAIL;
            } else if (riscv_supports_extension(target, 'D')) {
                riscv_program_insert(&program, fmv_x_d(S0, number - GDB_REGNO_FPR0));
            } else {
                riscv_program_insert(&program, fmv_x_w(S0, number - GDB_REGNO_FPR0));
            }
        } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
            riscv_program_csrr(&program, S0, number);
        } else {
            LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
            return ERROR_FAIL;
        }

主要就是兜出類似下面的內容(假設要讀取CSR):

csrrw      $s0, <TARGET>, $s0
fence
ebreak

最後一樣,先執行Program Buffer:

        /* Execute program. */
        result = riscv_program_exec(&program, target);

然後把結果讀取回來:

            /* Read S0 */
            if (register_read_direct(target, value, GDB_REGNO_S0) != ERROR_OK)
                return ERROR_FAIL;

以上就是 簡單的 讀取Register源碼剖析!
  
  
  

3. Write Registers

  
  

3.1 Write Registers by Abstract Command

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

static int register_set(struct reg *reg, uint8_t *buf)
{
    struct target *target = (struct target *) reg->arch_info;

    uint64_t value = buf_get_u64(buf, 0, reg->size);

    LOG_DEBUG("write 0x%" PRIx64 " to %s", value, reg->name);
    struct reg *r = &target->reg_cache->reg_list[reg->number];
    r->valid = true;
    memcpy(r->value, buf, (r->size + 7) / 8);

    riscv_set_register(target, reg->number, value);
    return ERROR_OK;
}

int riscv_set_register(struct target *target, enum gdb_regno r, riscv_reg_t v)
{
    return riscv_set_register_on_hart(target, riscv_current_hartid(target), r, v);
}

int riscv_set_register_on_hart(struct target *target, int hartid,
        enum gdb_regno regid, uint64_t value)
{
    RISCV_INFO(r);
    LOG_DEBUG("[%d] %s <- %" PRIx64, hartid, gdb_regno_name(regid), value);
    assert(r->set_register);
    return r->set_register(target, hartid, regid, value);
}

跟Read Register蠻類似的~~! 不多說,我們只關心set_register()的處理!
請參考以下內容(src/target/riscv/riscv-013.c):

static int riscv013_set_register(struct target *target, int hid, int rid, uint64_t value)
{
    LOG_DEBUG("writing 0x%" PRIx64 " to register %s on hart %d", value,
            gdb_regno_name(rid), hid);

    riscv_set_current_hartid(target, hid);

    if (rid <= GDB_REGNO_XPR31) {
        return register_write_direct(target, rid, value);
    } else if (rid == GDB_REGNO_PC) {
        LOG_DEBUG("writing PC to DPC: 0x%016" PRIx64, value);
        register_write_direct(target, GDB_REGNO_DPC, value);
        uint64_t actual_value;
        register_read_direct(target, &actual_value, GDB_REGNO_DPC);
        LOG_DEBUG("  actual DPC written: 0x%016" PRIx64, actual_value);
        if (value != actual_value) {
            LOG_ERROR("Written PC (0x%" PRIx64 ") does not match read back "
                    "value (0x%" PRIx64 ")", value, actual_value);
            return ERROR_FAIL;
        }
    } else if (rid == GDB_REGNO_PRIV) {
        uint64_t dcsr;
        register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
        dcsr = set_field(dcsr, CSR_DCSR_PRV, value);
        return register_write_direct(target, GDB_REGNO_DCSR, dcsr);
    } else {
        return register_write_direct(target, rid, value);
    }

    return ERROR_OK;
}

提醒一下,在Debug Mode中對$PC的寫入就是要對$DPC做寫入,別弄錯啦!!
最後是核心register_write_direct()的部分,請參考以下內容(src/target/riscv/riscv-013.c):

static int register_write_direct(struct target *target, unsigned number,
        uint64_t value)
{
    LOG_DEBUG("[%d] reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
            number, value);

    ///譯註: 先試著利用Abstract Command!
    int result = register_write_abstract(target, number, value,
            riscv_xlen(target));
    if (result == ERROR_OK)
        return ERROR_OK;

    struct riscv_program program;
    riscv_program_init(&program, target);

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

    if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
            riscv_supports_extension(target, 'D') &&
            riscv_xlen(target) < 64) {
        /* There are no instructions to move all the bits from a register, so
         * we need to use some scratch RAM. */
        riscv_program_insert(&program, fld(number - GDB_REGNO_FPR0, S0, 0));

        scratch_mem_t scratch;
        if (scratch_find(target, &scratch, &program, 8) != ERROR_OK)
            return ERROR_FAIL;

        if (register_write_direct(target, GDB_REGNO_S0, scratch.hart_address)
                != ERROR_OK)
            return ERROR_FAIL;

        if (scratch_write64(target, &scratch, value) != ERROR_OK)
            return ERROR_FAIL;

    } else {
        if (register_write_direct(target, GDB_REGNO_S0, value) != ERROR_OK)
            return ERROR_FAIL;

        if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
            if (riscv_supports_extension(target, 'D'))
                riscv_program_insert(&program, fmv_d_x(number - GDB_REGNO_FPR0, S0));
            else
                riscv_program_insert(&program, fmv_w_x(number - GDB_REGNO_FPR0, S0));
        } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
            riscv_program_csrw(&program, S0, number);
        } else {
            LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
            return ERROR_FAIL;
        }
    }

    int exec_out = riscv_program_exec(&program, target);

    /* Restore S0. */
    if (register_write_direct(target, GDB_REGNO_S0, s0) != ERROR_OK)
        return ERROR_FAIL;

    return exec_out;
}

內容其實跟register_read_direct()差不多!
一樣先用register_write_abstract()試試看!

static int register_write_abstract(struct target *target, uint32_t number,
        uint64_t value, unsigned size)
{
    RISCV013_INFO(info);

    if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
            !info->abstract_write_fpr_supported)
        return ERROR_FAIL;
    if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
            !info->abstract_write_csr_supported)
        return ERROR_FAIL;

    uint32_t command = access_register_command(number, size,
            AC_ACCESS_REGISTER_TRANSFER |
            AC_ACCESS_REGISTER_WRITE);      ///譯註: 多了WRITE

    if (write_abstract_arg(target, 0, value) != ERROR_OK)
        return ERROR_FAIL;

    int result = execute_abstract_command(target, command);
    if (result != ERROR_OK) {
        if (info->cmderr == CMDERR_NOT_SUPPORTED) {
            if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
                info->abstract_write_fpr_supported = false;
                LOG_INFO("Disabling abstract command writes to FPRs.");
            } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
                info->abstract_write_csr_supported = false;
                LOG_INFO("Disabling abstract command writes to CSRs.");
            }
        }
        return result;
    }

    return ERROR_OK;
}

比較需要注意的地方是Command,需要把Write給拉上:

    uint32_t command = access_register_command(number, size,
            AC_ACCESS_REGISTER_TRANSFER |
            AC_ACCESS_REGISTER_WRITE);

  
  

3.2 Write Registers by Program Buffer

至於使用Program Buffer的部分,流程就很簡單啦:

        if (register_write_direct(target, GDB_REGNO_S0, value) != ERROR_OK)
            return ERROR_FAIL;

        if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
            if (riscv_supports_extension(target, 'D'))
                riscv_program_insert(&program, fmv_d_x(number - GDB_REGNO_FPR0, S0));
            else
                riscv_program_insert(&program, fmv_w_x(number - GDB_REGNO_FPR0, S0));
        } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
            riscv_program_csrw(&program, S0, number);
        } else {
            LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
            return ERROR_FAIL;
        }

先把要寫入的資料放入$S0中,然後透過Program Buffer寫入!
搞定收工!
  
  
  

99. 結語

打到這篇的時候,突然發現內容實在太多了,原本想講完Reg/Memory的Access
但是Memory Access的部分..... 真是尤其的多~
只好再拆成另一篇......
  
  
  

參考資料

  1. RISC-V External Debug Support 0.13
  2. GitHub: riscv/riscv-openocd
  3. [Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands](https://ithelp.ithome.com.tw/articles/10194265
  4. Day 11: RISC-V Debug Introduction

上一篇
Day 17: 深入淺出 RISC-V 源碼剖析 (2) - Target Status Control
下一篇
Day 19: 深入淺出 RISC-V 源碼剖析 (4) - Memory Access
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言