挖~! 終於來到第三篇啦!!!
2018年最值得高興的事就是把公司的桌電升級成SSD了~!!!
速度果然飛快啊!!
不過呢,一樣,今天還是來繼續源碼剖析一下!!
把程式碼塞好、塞滿!
來看看RISC-V中Register Access的部分!
還記的在「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」中有提到Debug System中讀取Register的方式分成兩種:
那篇其實已經有解釋這一部分了,不過這邊會講得更仔細些!
先來看看進入點,參考以下內容(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;
}
回到原本的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源碼剖析!
先來看看進入點,參考以下內容(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);
至於使用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寫入!
搞定收工!
打到這篇的時候,突然發現內容實在太多了,原本想講完Reg/Memory的Access
但是Memory Access的部分..... 真是尤其的多~
只好再拆成另一篇......