經過前面五篇的洗禮後,終於來到本系列最後一篇啦!!!
先幫各位複習一下前面那幾篇的連結:
大部分的應用&流程都已經剖析過,本篇算是總結?!
順便講講剩下的幾個東西! 讓我們輕鬆地結束吧!
OpenOCD在初始化Target的時候,同時也會註冊一個target_register_timer_callback()
,主要的目的是定期去查詢Target的狀態(預設: 100ms一次),可以參考以下實作,
參考以下內容(src/target/target.c):
static int target_init(struct command_context *cmd_ctx)
{
struct target *target;
int retval;
for (target = all_targets; target; target = target->next) {
retval = target_init_one(cmd_ctx, target);
if (ERROR_OK != retval)
return retval;
}
if (!all_targets)
return ERROR_OK;
retval = target_register_user_commands(cmd_ctx);
if (ERROR_OK != retval)
return retval;
///譯註: 這裡註冊!
retval = target_register_timer_callback(&handle_target,
polling_interval, 1, cmd_ctx->interp);
if (ERROR_OK != retval)
return retval;
return ERROR_OK;
}
再來handle_target()中,就會掃描所有註冊的Target,在未Disable之前,
則會呼叫各個Target中所註冊的Polling函式,請參考以下內容(src/target/target.c):
/* process target state changes */
static int handle_target(void *priv)
{
....前面省略
/* Poll targets for state changes unless that's globally disabled.
* Skip targets that are currently disabled.
*/
///譯註: 搜尋註冊中的所有Target
for (struct target *target = all_targets;
is_jtag_poll_safe() && target;
target = target->next) {
///譯註: 跳過那些沒有被初始化成功的Target!
if (!target_was_examined(target))
continue;
///譯註: 跳過那些已經被Disable的Target
if (!target->tap->enabled)
continue;
if (target->backoff.times > target->backoff.count) {
/* do not poll this time as we failed previously */
target->backoff.count++;
continue;
}
target->backoff.count = 0;
/* only poll target if we've got power and srst isn't asserted */
if (!powerDropout && !srstAsserted) {
/* polling may fail silently until the target has been examined */
///譯註: 這邊呼叫處理!
retval = target_poll(target);
....中間省略
}
}
return retval;
}
再來就是負責呼叫和處理各種Target的函式,請參考以下內容(src/target/target.c):
int target_poll(struct target *target)
{
int retval;
/* We can't poll until after examine */
if (!target_was_examined(target)) {
/* Fail silently lest we pollute the log */
return ERROR_FAIL;
}
///譯註: 呼叫各個Target中預先註冊好的函式來處理!
retval = target->type->poll(target);
if (retval != ERROR_OK)
return retval;
if (target->halt_issued) {
if (target->state == TARGET_HALTED)
target->halt_issued = false;
else {
int64_t t = timeval_ms() - target->halt_issued_time;
if (t > DEFAULT_HALT_TIMEOUT) {
target->halt_issued = false;
LOG_INFO("Halt timed out, wake up GDB.");
target_call_event_callbacks(target, TARGET_EVENT_GDB_HALT);
}
}
}
return ERROR_OK;
}
在各個Target中,已經會預先註冊好處理的流程!
所以這邊就簡單啦,直接呼叫target->type->poll()
即可!
再來我們就進到RISC-V中!
首先讓我們先看一下上層處理的部分,請參考以下內容(src/target/riscv/riscv.c):
int riscv_openocd_poll(struct target *target)
{
LOG_DEBUG("polling all harts");
int triggered_hart = -1;
if (riscv_rtos_enabled(target)) {
....RTOS的部分省略!
} else {
////譯註: 針對單一個Hart呼叫riscv_poll_hart()來處理!
if (riscv_poll_hart(target, riscv_current_hartid(target)) == 0)
return ERROR_OK;
triggered_hart = riscv_current_hartid(target);
LOG_DEBUG(" hart %d halted", triggered_hart);
}
....後面Halt處理省略
target->state = TARGET_HALTED;
target_call_event_callbacks(target, TARGET_EVENT_HALTED);
return ERROR_OK;
}
這邊很簡單,單純的呼叫riscv_poll_hart()來處理,所以接下來就是來關心一下riscv_poll_hart()
的流程,請參考以下內容(src/target/riscv/riscv.c):
static int riscv_poll_hart(struct target *target, int hartid)
{
RISCV_INFO(r);
riscv_set_current_hartid(target, hartid);
LOG_DEBUG("polling hart %d, target->state=%d (TARGET_HALTED=%d)", hartid, target->state, TARGET_HALTED);
/* If OpenOCD this we're running but this hart is halted then it's time
* to raise an event. */
///譯註: 判斷Hart是否在Halt的階段
if (target->state != TARGET_HALTED && riscv_is_halted(target)) {
LOG_DEBUG(" triggered a halt");
r->on_halt(target);
return 1;
}
return 0;
}
這邊就是簡單的呼叫riscv_is_halted()
來判斷Hart目前的狀態,請參考以下內容(src/target/riscv/riscv.c):
bool riscv_is_halted(struct target *target)
{
RISCV_INFO(r);
assert(r->is_halted);
return r->is_halted(target);
}
這邊終於呼叫到0.13的底層來處理is_halted(),
所以我們來看看核心的處理部分,請參考以下內容(src/target/riscv/riscv-013.c):
static bool riscv013_is_halted(struct target *target)
{
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
if (get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL))
LOG_ERROR("hart %d is unavailiable", riscv_current_hartid(target));
if (get_field(dmstatus, DMI_DMSTATUS_ANYNONEXISTENT))
LOG_ERROR("hart %d doesn't exist", riscv_current_hartid(target));
return get_field(dmstatus, DMI_DMSTATUS_ALLHALTED);
}
基本上就是從$dmstatus中挖出allhalted這個欄位來看看,忘記的話可以參考「」中"2.2 0x11 dmstatus: Debug Module Status"的說明!
本節主要是依據上節的內容作延伸,當Target進入Halted的時候,
OpenOCD就會負責判斷原因!! 並在適當的時候做出處理或是告知使用者!
所以我們先來回顧一下,剛剛介紹過得riscv_openocd_poll()
,
請參考以下內容(src/target/riscv/riscv.c):
int riscv_openocd_poll(struct target *target)
{
....前面介紹過了,省略
target->state = TARGET_HALTED;
///譯註: 呼叫riscv_halt_reason()
switch (riscv_halt_reason(target, triggered_hart)) {
case RISCV_HALT_BREAKPOINT:
target->debug_reason = DBG_REASON_BREAKPOINT;
break;
case RISCV_HALT_INTERRUPT:
target->debug_reason = DBG_REASON_DBGRQ;
break;
case RISCV_HALT_SINGLESTEP:
target->debug_reason = DBG_REASON_SINGLESTEP;
break;
case RISCV_HALT_UNKNOWN:
target->debug_reason = DBG_REASON_UNDEFINED;
break;
}
if (riscv_rtos_enabled(target)) {
target->rtos->current_threadid = triggered_hart + 1;
target->rtos->current_thread = triggered_hart + 1;
}
target->state = TARGET_HALTED;
target_call_event_callbacks(target, TARGET_EVENT_HALTED);
return ERROR_OK;
}
這邊主要是接續在剛剛riscv_poll_hart()後面,當判斷Hart進入Halted時,才會進入到這個檢測得流程中,
然後呼叫riscv_halt_reason()來判斷原因,讓我們繼續看下去,
請參考以下內容(src/target/riscv/riscv.c):
enum riscv_halt_reason riscv_halt_reason(struct target *target, int hartid)
{
RISCV_INFO(r);
riscv_set_current_hartid(target, hartid);
if (!riscv_is_halted(target)) {
LOG_ERROR("Hart is not halted!");
return RISCV_HALT_UNKNOWN;
}
return r->halt_reason(target);
}
這邊就是很單純的呼叫底層的r->halt_reason()
來判斷,所以.... 可以直接跳過介紹!
然後就來到最核心的地方riscv013_halt_reason()
,
請參考以下內容(src/target/riscv/riscv-013.c):
static enum riscv_halt_reason riscv013_halt_reason(struct target *target)
{
uint64_t dcsr = riscv_get_register(target, GDB_REGNO_DCSR);
switch (get_field(dcsr, CSR_DCSR_CAUSE)) {
case CSR_DCSR_CAUSE_SWBP:
case CSR_DCSR_CAUSE_TRIGGER:
return RISCV_HALT_BREAKPOINT;
case CSR_DCSR_CAUSE_STEP:
return RISCV_HALT_SINGLESTEP;
case CSR_DCSR_CAUSE_DEBUGINT:
case CSR_DCSR_CAUSE_HALT:
return RISCV_HALT_INTERRUPT;
}
LOG_ERROR("Unknown DCSR cause field: %x", (int)get_field(dcsr, CSR_DCSR_CAUSE));
LOG_ERROR(" dcsr=0x%016lx", (long)dcsr);
return RISCV_HALT_UNKNOWN;
}
這邊主要用到$dcsr中的cause欄位,可以參考「Day 11: RISC-V Debug Introduction」中"#5.1 0x7b0 dcsr: Debug Control and Status"的相關說明!
主要我們將原因分成以下三類:
這邊就是簡單的判斷cause欄位,然後回傳對應的原因代號給上層!
最後回來看一下riscv_openocd_poll()處理的流程
switch (riscv_halt_reason(target, triggered_hart)) {
case RISCV_HALT_BREAKPOINT:
target->debug_reason = DBG_REASON_BREAKPOINT;
break;
case RISCV_HALT_INTERRUPT:
target->debug_reason = DBG_REASON_DBGRQ;
break;
case RISCV_HALT_SINGLESTEP:
target->debug_reason = DBG_REASON_SINGLESTEP;
break;
case RISCV_HALT_UNKNOWN:
target->debug_reason = DBG_REASON_UNDEFINED;
break;
}
....中間省略
target->state = TARGET_HALTED;
target_call_event_callbacks(target, TARGET_EVENT_HALTED);
這邊就是把原因放回去OpenOCD Target資料結構中的debug_reason,
最後呼叫OpenOCD系統的callback去做後續處理target_call_event_callbacks()
!
以上大概就是簡單介紹的這邊,將整個系列的"深入淺出 RISC-V 源碼剖析"畫下一個句點!然後鐵人賽就結束了,沒東西可以寫....
明天將會帶一個簡單的Lab,操作OpenOCD在RISC-V 64bit的平台上做個簡單的DEMO!