我的媽呀~~ 終於過了三分之一!
上篇簡單的介紹了Debug Module中核心功能: Program Buffer和Abstract Commands,
那Debug Module基本上也都差不多講完了!
本篇主要是介紹Debug Module中常用到的Registers和其中每個Bits所代表的意義,
Debugger就是透過對這些Register的讀/寫來達到控制整個Target的動作!
以下內容比較繁瑣一些,基本上就跟RISC-V External Debug Support 0.13 的內容差不多,只不過是中文的XD!
本文主要會介紹以下幾個Registers
硬是要分成兩節~~~~
---引用自RISC-V External Debug Support 0.13
以下是幾個簡單的實作用來說明如何使用這個Register,
首先是haltreq,請參考(src/target/riscv/riscv-013.c)
static int riscv013_halt_current_hart(struct target *target)
{
RISCV_INFO(r);
LOG_DEBUG("halting hart %d", r->current_hartid);
if (riscv_is_halted(target))
LOG_ERROR("Hart %d is already halted!", r->current_hartid);
/* Issue the halt command, and then wait for the current hart to halt. */
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
///譯註: 將haltreq拉成1,讓Target進入Halt
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 1);
dmi_write(target, DMI_DMCONTROL, dmcontrol);
for (size_t i = 0; i < 256; ++i)
if (riscv_is_halted(target))
break;
if (!riscv_is_halted(target)) {
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
dmcontrol = dmi_read(target, DMI_DMCONTROL);
LOG_ERROR("unable to halt hart %d", r->current_hartid);
LOG_ERROR(" dmcontrol=0x%08x", dmcontrol);
LOG_ERROR(" dmstatus =0x%08x", dmstatus);
return ERROR_FAIL;
}
///譯註: 確認Target進入Halt階段後,記得清掉haltreq!!
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 0);
dmi_write(target, DMI_DMCONTROL, dmcontrol);
return ERROR_OK;
}
再來是resumereq,請參考(src/target/riscv/riscv-013.c)
static int riscv013_step_or_resume_current_hart(struct target *target, bool step)
{
RISCV_INFO(r);
LOG_DEBUG("resuming hart %d (for step?=%d)", r->current_hartid, step);
if (!riscv_is_halted(target)) {
LOG_ERROR("Hart %d is not halted!", r->current_hartid);
return ERROR_FAIL;
}
struct riscv_program program;
riscv_program_init(&program, target);
riscv_program_fence_i(&program);
if (riscv_program_exec(&program, target) != ERROR_OK)
return ERROR_FAIL;
/* Issue the resume command, and then wait for the current hart to resume. */
///譯註: 將resumereq拉成1,讓Target進入Resume
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 1);
dmi_write(target, DMI_DMCONTROL, dmcontrol);
for (size_t i = 0; i < 256; ++i) {
usleep(10);
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
if (get_field(dmstatus, DMI_DMSTATUS_ALLRESUMEACK) == 0)
continue;
if (step && get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0)
continue;
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 0);
dmi_write(target, DMI_DMCONTROL, dmcontrol);
return ERROR_OK;
}
....後面省略!
}
hartreset和ndmreset的實作可以參考「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」中的"# 2.1 Reset Control"的部分,這邊不多加贅述!
最後是dmactive的部分,請參考(src/target/riscv/riscv-013.c)
static int examine(struct target *target)
{
....前面不重要,省略
/* 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);
....中間不重要,省略
if (!get_field(dmcontrol, DMI_DMCONTROL_DMACTIVE)) {
LOG_ERROR("Debug Module did not become active. dmcontrol=0x%x",
dmcontrol);
return ERROR_FAIL;
}
....後面不重要,省略
}
---引用自RISC-V External Debug Support 0.13
乍看之下覺得會很複雜,但仔細看一下,他都是all加上any兩兩一組的,
底下也按照這樣分類解說!
以下是一個簡單的實作,用來得知Hart目前的狀況!
請參考(src/target/riscv/riscv-013.c)
static int examine(struct target *target)
{
....前面不重要,省略
///譯註: 這邊判斷Debug Module的版本是否為0.13
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);
....中間不重要,省略
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;
}
....後面不重要,省略
}
---引用自RISC-V External Debug Support 0.13
這個Register是用來標示"Program Buffer"和"Abstract Command"的一些資本資訊和狀態用的!
實作的部分放在下面"# 2.4 0x17: Abstract Command"一起看!!
---引用自RISC-V External Debug Support 0.13
這部分的內容在「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」中的"# 2. Abstract Commands Overview"有提到過,基本上將Command組好後,寫入這個Register中就會開始執行!
連同上面「0x16 abstractcs: Abstract Control and Status」一起看!
請參考(src/target/riscv/riscv-013.c)
static int wait_for_idle(struct target *target, uint32_t *abstractcs)
{
RISCV013_INFO(info);
time_t start = time(NULL);
while (1) {
*abstractcs = dmi_read(target, DMI_ABSTRACTCS);
///譯註: 判斷是否還在執行中!
if (get_field(*abstractcs, DMI_ABSTRACTCS_BUSY) == 0)
return ERROR_OK;
///譯註: 這邊有加上timeout的機制,避免Abstract Command一直執行不完!
if (time(NULL) - start > riscv_command_timeout_sec) {
info->cmderr = get_field(*abstractcs, DMI_ABSTRACTCS_CMDERR);
if (info->cmderr != CMDERR_NONE) {
const char *errors[8] = {
"none",
"busy",
"not supported",
"exception",
"halt/resume",
"reserved",
"reserved",
"other" };
LOG_ERROR("Abstract command ended in error '%s' (abstractcs=0x%x)",
errors[info->cmderr], *abstractcs);
}
LOG_ERROR("Timed out after %ds waiting for busy to go low (abstractcs=0x%x). "
"Increase the timeout with riscv set_command_timeout_sec.",
riscv_command_timeout_sec,
*abstractcs);
return ERROR_FAIL;
}
}
}
static int execute_abstract_command(struct target *target, uint32_t command)
{
RISCV013_INFO(info);
LOG_DEBUG("command=0x%x", command);
///譯註: 將組合好的Command法送出去執行
dmi_write(target, DMI_COMMAND, command);
{
uint32_t dmstatus = 0;
///譯註: 這邊會等待執行完畢!
wait_for_idle(target, &dmstatus);
}
///譯註: 確認Abstract Command是否有發生過錯誤!
uint32_t cs = dmi_read(target, DMI_ABSTRACTCS);
info->cmderr = get_field(cs, DMI_ABSTRACTCS_CMDERR);
if (info->cmderr != 0) {
LOG_DEBUG("command 0x%x failed; abstractcs=0x%x", command, cs);
/* Clear the error. */
dmi_write(target, DMI_ABSTRACTCS, set_field(0, DMI_ABSTRACTCS_CMDERR,
info->cmderr));
return ERROR_FAIL;
}
return ERROR_OK;
}
介紹完上面的部分後,最後來回顧一下Target的States部分,
並把「Day 08: RISC-V Debug Module (上篇): Overview & Target Status Control」和「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」做個總結的回顧!
先上圖 (他終於是彩色的~~~
---引用自RISC-V External Debug Support 0.13
首先看到中間 綠色框起來 的地方,當Hart踩到Breakpoint/Watchpoint/ebreak等等進入Debug Mode或是Debugger將dmcontrol的halreq拉起來的時候,Hart就會進入"Halting"的階段,確定停下來後,就將dmstatus的allhalted拉成1,進入"Halting: Waiting",等待Debugger的介入!
Resum也是同樣的方法,當Debugger將dmcontrol的resumereq拉起來後,Target會進入"Resuming"的狀態,直到確認開始Runing後,就將dmstatus的allrunning拉成1,此時就回到"M/U/S Mode(Runing)"!
參考以下實作方式(src/target/riscv/riscv-013.c)
int riscv013_execute_debug_buffer(struct target *target)
{
uint32_t run_program = 0;
run_program = set_field(run_program, AC_ACCESS_REGISTER_SIZE, 2);
run_program = set_field(run_program, AC_ACCESS_REGISTER_POSTEXEC, 1);///譯註: 重點是這邊postexec要設定成1
run_program = set_field(run_program, AC_ACCESS_REGISTER_TRANSFER, 0);
run_program = set_field(run_program, AC_ACCESS_REGISTER_REGNO, 0x1000);
return execute_abstract_command(target, run_program);
}
再來看到 右下紅色框起來 的地方,當在Halted的階段中,如果對commnd寫入,並將command的cmdtype設定成"0",則會進入到"Command: Start"這個階段,此時分成兩種功能:
先講Program Buffer Executing的地方,當command中的postexec設定成1且transfer設成0,則會進入"Command: ProgBuf",並開始執行整個Program Buffer,當Program Buffer的最後一條指令"ebreak"執行後,就會回到"Commmand: Done"的階段,整個Program Buffer的執行就告一段落!
再來是Access Register的部分,當command中的transfer設定成1時候,就會進入到"Command: Transfer"的階段,並依照command中的write和regno來決定讀/寫和目標的Register,
詳細操作可以參考: Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands!
同樣的,當完成後,就會回到"Commmand: Done"的階段,整個Access Register的執行就算告一段落!
程式碼的部分Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands中有說明過,不過好像忘記放上command組成的部分,這邊補上~~!
參考以下實作方式(src/target/riscv/riscv-013.c)
static uint32_t access_register_command(uint32_t number, unsigned size,
uint32_t flags)
{
///譯註: cmdtype設成0=>Access Register
uint32_t command = set_field(0, DMI_COMMAND_CMDTYPE, 0);
///譯註: 按照Register的長度,決定size
switch (size) {
case 32:
command = set_field(command, AC_ACCESS_REGISTER_SIZE, 2);
break;
case 64:
command = set_field(command, AC_ACCESS_REGISTER_SIZE, 3);
break;
default:
assert(0);
}
///譯註: 依照目標的Register,決定regno
if (number <= GDB_REGNO_XPR31) {
command = set_field(command, AC_ACCESS_REGISTER_REGNO,
0x1000 + number - GDB_REGNO_ZERO);
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
command = set_field(command, AC_ACCESS_REGISTER_REGNO,
0x1020 + number - GDB_REGNO_FPR0);
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
command = set_field(command, AC_ACCESS_REGISTER_REGNO,
number - GDB_REGNO_CSR0);
} else {
assert(0);
}
command |= flags;
return command;
}
這部分目前還有看到有實作的地方,先跳過~!!!!!!
日後官方有完成的話,再回來填這個坑!
終於將整個Debug Module稍微的介紹完畢!!
希望能夠帶給讀者了解整個Debug系統是如何運作的!!
明天將更深入Hart中,看看在Hart中的Debug Mode是如何運作!