在上篇「Day 15: 讓百萬人都驚呆的Debug Transport Module~~(下)」中有提到"明天開始將進入到全新的領域--FTDI Based Adapter,
講述從PC端到Target端中間的Debug Trasnport Hardware的相關"!?
不過仔細想想才發現,好像還沒有介紹怎麼使用OpenOCD在RISC-V的架構中Debug!
而且前面從Day 07到Day 15都是很零碎的講解External Debug System~~
所以呢.... 從本篇開始,預計4~5篇文章,再加上1個簡單的Lab來剖析一下OpenOCD是如何支援RISC-V架構的Target!
那我們開始吧!
在OpenOCD中,如果要先增Target,則必須先在src/target/Makefile.am
中,
註冊實作Target的Source/Header檔案,
讓OpenOCD在編譯的時候可以順利找到你寫好的那些東西!
如以下範例(src/target/Makefile.am):
noinst_LTLIBRARIES += %D%/libtarget.la
%C%_libtarget_la_SOURCES = \
....前面省略
$(RISCV_SRC) \
....後面省略
....中間其他平台的設定,省略!
RISCV_SRC = \
%D%/riscv/riscv-011.c \
%D%/riscv/riscv-013.c \
%D%/riscv/riscv.c \
%D%/riscv/program.c \
%D%/riscv/batch.c
....後面省略
接著在src/target/target.c
中註冊一個struct target_type <Target_Name>
當作這個Target相關支援的進入點!
如以下範例(src/target/target.c):
extern struct target_type riscv_target;
static struct target_type *target_types[] = {
...前面省略...
&nds32_v2_target, /// 譯註: 硬是要廣告! 這是Andes V2 架構的進入點!!!
&nds32_v3_target, /// 譯註: 硬是要廣告! 這是Andes V3 架構的進入點!!!
&nds32_v3m_target, /// 譯註: 硬是要廣告! 這是Andes V3m 架構的進入點!!!
...中間省略...
&riscv_target, /// 譯註: 本文的重點!
...後面省略...
NULL,
};
那這個進入點裡面包含什麼呢!?
讓我們參考以下內容(src/target/riscv/riscv.c):
struct target_type riscv_target = {
.name = "riscv",
.init_target = riscv_init_target,
.deinit_target = riscv_deinit_target,
.examine = riscv_examine,
/* poll current target status */
.poll = old_or_new_riscv_poll,
.halt = old_or_new_riscv_halt,
.resume = old_or_new_riscv_resume,
.step = old_or_new_riscv_step,
.assert_reset = riscv_assert_reset,
.deassert_reset = riscv_deassert_reset,
.read_memory = riscv_read_memory,
.write_memory = riscv_write_memory,
.blank_check_memory = riscv_blank_check_memory,
.checksum_memory = riscv_checksum_memory,
.get_gdb_reg_list = riscv_get_gdb_reg_list,
.add_breakpoint = riscv_add_breakpoint,
.remove_breakpoint = riscv_remove_breakpoint,
.add_watchpoint = riscv_add_watchpoint,
.remove_watchpoint = riscv_remove_watchpoint,
.arch_state = riscv_arch_state,
.run_algorithm = riscv_run_algorithm,
.commands = riscv_command_handlers
};
你會發現,除了定義這個Target的名稱叫"riscv"
外,還有連結了不少"Function Pointer"來指向實作的部分!
從本篇開始,預計4~5篇文章,將會分成以下幾類,分別詳細剖析原始碼:
最後再搭配一篇Lab用OpenOCD來操作RISC-V的小實驗!
這邊比較簡單一些,主要是在OpenOCD讀到Config中"init"後,就會對每個Target去呼叫type->init_target()
來做內部資料的一些初始化!
RISC-V Target初始化的方式如下(src/target/riscv/riscv.c):
static int riscv_init_target(struct command_context *cmd_ctx,
struct target *target)
{
LOG_DEBUG("riscv_init_target()");
target->arch_info = calloc(1, sizeof(riscv_info_t)); ///譯註: 首先註冊一個riscv_info_t的arch_info用來儲存Target相關的一些資料
if (!target->arch_info)
return ERROR_FAIL;
riscv_info_t *info = (riscv_info_t *) target->arch_info;
riscv_info_init(target, info); ///譯註: 初始化這個資料結構
info->cmd_ctx = cmd_ctx;
select_dtmcontrol.num_bits = target->tap->ir_length;
select_dbus.num_bits = target->tap->ir_length;
select_idcode.num_bits = target->tap->ir_length;
return ERROR_OK;
}
其中
select_dtmcontrol.num_bits = target->tap->ir_length;
select_dbus.num_bits = target->tap->ir_length;
select_idcode.num_bits = target->tap->ir_length;
這部分如果之後文章中有提到JTAG底層操作的時候會再詳細提到這部分,這邊先跳過!!
OpenOCD允許Target在內部儲存自己所需要用到的所有變數和資料、輔助的函數等等,
並利用target->arch_info
指向這個儲存的地方!
當然,這個資料也要初始化,所以我們來看看以下實作(src/target/riscv/riscv.c):
void riscv_info_init(struct target *target, riscv_info_t *r)
{
memset(r, 0, sizeof(*r));
r->dtm_version = 1;
r->registers_initialized = false;
r->current_hartid = target->coreid;
memset(r->trigger_unique_id, 0xff, sizeof(r->trigger_unique_id));
for (size_t h = 0; h < RISCV_MAX_HARTS; ++h) {
r->xlen[h] = -1; ///譯註: 假設所有Target皆是UNKNOWN-bits
for (size_t e = 0; e < RISCV_MAX_REGISTERS; ++e)
r->valid_saved_registers[h][e] = false;
}
}
這只是內部資料處理的部分,當然,OpenOCD還沒有真正連上Target,
因此對於Target的相關資訊也還沒辦法知道!
因此在下一節中,將探討OpenOCD如何去偵測Target的必要資訊!
不囉唆! 先上程式碼!
參考以下內容(src/target/riscv/riscv.c):
static int riscv_examine(struct target *target)
{
LOG_DEBUG("riscv_examine()");
if (target_was_examined(target)) {
LOG_DEBUG("Target was already examined.");
return ERROR_OK;
}
/* Don't need to select dbus, since the first thing we do is read dtmcontrol. */
riscv_info_t *info = (riscv_info_t *) target->arch_info;
///譯註: 利用dtmcontrol_scan來得到DTM中$dtmsc的值!
uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
info->dtm_version = get_field(dtmcontrol, DTMCONTROL_VERSION); ///譯註: 判斷Debug System的版本0.11/0.13/...!
LOG_DEBUG(" version=0x%x", info->dtm_version);
struct target_type *tt = get_target_type(target);
if (tt == NULL)
return ERROR_FAIL;
int result = tt->init_target(info->cmd_ctx, target);
if (result != ERROR_OK)
return result;
return tt->examine(target);
}
還記得「Day 14: 讓百萬人都驚呆的Debug Transport Module~~(上)」中介紹到的$dtmsc嗎XD!?
忘記的話可以參考一下"2.2 0x10 dtmsc: DTM Control and Status"的部分!!
在這邊中,最重要的是讀取出version這個欄位的資料,因為在實作中分成0.11(riscv-011.c)和0.13(riscv-013.c)這兩部分,這邊就是要判斷Target內External Debug System的版本,這樣才可以呼叫對應的函式來做更進一步的初始化tt->examine(target)
!
當然本文還是以0.13為主,所以,一樣,先上程式碼,讓我們看看0.13中是如何做更進一步的處理!
參考以下內容(src/target/riscv/riscv-013.c):
static int examine(struct target *target)
{
/* Don't need to select dbus, since the first thing we do is read dtmcontrol. */
uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
LOG_DEBUG(" dmireset=%d", get_field(dtmcontrol, DTM_DTMCS_DMIRESET));
LOG_DEBUG(" idle=%d", get_field(dtmcontrol, DTM_DTMCS_IDLE));
LOG_DEBUG(" dmistat=%d", get_field(dtmcontrol, DTM_DTMCS_DMISTAT));
LOG_DEBUG(" abits=%d", get_field(dtmcontrol, DTM_DTMCS_ABITS));
LOG_DEBUG(" version=%d", get_field(dtmcontrol, DTM_DTMCS_VERSION));
if (dtmcontrol == 0) {
LOG_ERROR("dtmcontrol is 0. Check JTAG connectivity/board power.");
return ERROR_FAIL;
}
if (get_field(dtmcontrol, DTM_DTMCS_VERSION) != 1) {
LOG_ERROR("Unsupported DTM version %d. (dtmcontrol=0x%x)",
get_field(dtmcontrol, DTM_DTMCS_VERSION), dtmcontrol);
return ERROR_FAIL;
}
riscv013_info_t *info = get_info(target);
info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
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);
uint32_t hartinfo = dmi_read(target, DMI_HARTINFO);
LOG_DEBUG("dmcontrol: 0x%08x", dmcontrol);
LOG_DEBUG("dmstatus: 0x%08x", dmstatus);
LOG_DEBUG("hartinfo: 0x%08x", hartinfo);
info->datasize = get_field(hartinfo, DMI_HARTINFO_DATASIZE);
info->dataaccess = get_field(hartinfo, DMI_HARTINFO_DATAACCESS);
info->dataaddr = get_field(hartinfo, DMI_HARTINFO_DATAADDR);
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;
}
/* Check that abstract data registers are accessible. */
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
info->datacount = get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT);
info->progbufsize = get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE);
/* Before doing anything else we must first enumerate the harts. */
RISCV_INFO(r);
r->impebreak = get_field(dmstatus, DMI_DMSTATUS_IMPEBREAK);
/* Don't call any riscv_* functions until after we've counted the number of
* cores and initialized registers. */
for (int i = 0; i < RISCV_MAX_HARTS; ++i) {
if (!riscv_rtos_enabled(target) && i != target->coreid)
continue;
r->current_hartid = i;
riscv013_select_current_hart(target);
uint32_t s = dmi_read(target, DMI_DMSTATUS);
if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
break;
r->hart_count = i + 1;
if (!riscv_is_halted(target))
riscv013_halt_current_hart(target);
/* Without knowing anything else we can at least mess with the
* program buffer. */
r->debug_buffer_size[i] = info->progbufsize;
int result = register_read_abstract(target, NULL, GDB_REGNO_S0, 64);
if (result == ERROR_OK)
r->xlen[i] = 64;
else
r->xlen[i] = 32;
register_read_direct(target, &r->misa, GDB_REGNO_MISA);
/* Now init registers based on what we discovered. */
if (riscv_init_registers(target) != ERROR_OK)
return ERROR_FAIL;
/* Display this as early as possible to help people who are using
* really slow simulators. */
LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, i, r->xlen[i],
r->misa);
}
LOG_DEBUG("Enumerated %d harts", r->hart_count);
/* Then we check the number of triggers availiable to each hart. */
riscv_enumerate_triggers(target);
/* Resumes all the harts, so the debugger can later pause them. */
/* TODO: Only do this if the harts were halted to start with. */
riscv_resume_all_harts(target);
target->state = TARGET_RUNNING;
target_set_examined(target);
if (target->rtos)
riscv_update_threads(target->rtos);
/* Some regression suites rely on seeing 'Examined RISC-V core' to know
* when they can connect with gdb/telnet.
* We will need to update those suites if we want to change that text. */
LOG_INFO("Examined RISC-V core; found %d harts",
riscv_count_harts(target));
for (int i = 0; i < riscv_count_harts(target); ++i) {
if (riscv_hart_enabled(target, i)) {
LOG_INFO(" hart %d: XLEN=%d, %d triggers", i, r->xlen[i],
r->trigger_count[i]);
} else {
LOG_INFO(" hart %d: currently disabled", i);
}
}
return ERROR_OK;
}
挖靠,這函數也太長了吧!
看得我都昏了.......
一樣,底下分成幾個小節來各個擊破!
首先看到這部分:
/* Don't need to select dbus, since the first thing we do is read dtmcontrol. */
uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
LOG_DEBUG(" dmireset=%d", get_field(dtmcontrol, DTM_DTMCS_DMIRESET));
LOG_DEBUG(" idle=%d", get_field(dtmcontrol, DTM_DTMCS_IDLE));
LOG_DEBUG(" dmistat=%d", get_field(dtmcontrol, DTM_DTMCS_DMISTAT));
LOG_DEBUG(" abits=%d", get_field(dtmcontrol, DTM_DTMCS_ABITS));
LOG_DEBUG(" version=%d", get_field(dtmcontrol, DTM_DTMCS_VERSION));
if (dtmcontrol == 0) {
LOG_ERROR("dtmcontrol is 0. Check JTAG connectivity/board power.");
return ERROR_FAIL;
}
if (get_field(dtmcontrol, DTM_DTMCS_VERSION) != 1) {
LOG_ERROR("Unsupported DTM version %d. (dtmcontrol=0x%x)",
get_field(dtmcontrol, DTM_DTMCS_VERSION), dtmcontrol);
return ERROR_FAIL;
}
riscv013_info_t *info = get_info(target);
info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS); ///譯註: 在DMI傳輸中,需要判斷a-bits的長度!
info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
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;
}
前面這部分,沒甚麼好說的,主要就是把Debug Transport Module和Debug Modue中比較重要的幾個Register 其實也才兩個 給讀出來!
忘記的話可以參考
這邊基本上除了印出每個欄位到Log中外,另外就是設定
abits和idle這兩部分
info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
再來就是比較重要的部分,對整個Debug Module去做Reset:
/* Reset the Debug Module. */
dmi_write(target, DMI_DMCONTROL, 0); ///譯註: Reset Debug Module就是在dmactive中寫入0
dmi_write(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE); ///譯註: 然後把它重啟
重啟之後,就繼續接下來初始化的部分:
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
uint32_t hartinfo = dmi_read(target, DMI_HARTINFO);
LOG_DEBUG("dmcontrol: 0x%08x", dmcontrol);
LOG_DEBUG("dmstatus: 0x%08x", dmstatus);
LOG_DEBUG("hartinfo: 0x%08x", hartinfo);
info->datasize = get_field(hartinfo, DMI_HARTINFO_DATASIZE);
info->dataaccess = get_field(hartinfo, DMI_HARTINFO_DATAACCESS);
info->dataaddr = get_field(hartinfo, DMI_HARTINFO_DATAADDR);
然後判斷一下Debug Module的狀態
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;
}
最後是判斷Debug Module支不支援Abstract Commands和Program Buffer
/* Check that abstract data registers are accessible. */
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
info->datacount = get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT);
info->progbufsize = get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE);
/* Before doing anything else we must first enumerate the harts. */
RISCV_INFO(r);
r->impebreak = get_field(dmstatus, DMI_DMSTATUS_IMPEBREAK);
忘記Abstract Commands和Program Buffer,可以回顧一下「Day 09: RISC-V Debug Module (中篇)-Program Buffer & Abstract Commands」!
以上就是針對Debug Module和相關資料進行初始化的部分!
底下實作,是針對每個Hart,去做初始化的部分:
for (int i = 0; i < RISCV_MAX_HARTS; ++i) {
if (!riscv_rtos_enabled(target) && i != target->coreid)
continue;
r->current_hartid = i;
riscv013_select_current_hart(target);
uint32_t s = dmi_read(target, DMI_DMSTATUS);
if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
break;
r->hart_count = i + 1;
if (!riscv_is_halted(target))
riscv013_halt_current_hart(target);
/* Without knowing anything else we can at least mess with the
* program buffer. */
r->debug_buffer_size[i] = info->progbufsize;
int result = register_read_abstract(target, NULL, GDB_REGNO_S0, 64);
if (result == ERROR_OK)
r->xlen[i] = 64;
else
r->xlen[i] = 32;
register_read_direct(target, &r->misa, GDB_REGNO_MISA);
/* Now init registers based on what we discovered. */
if (riscv_init_registers(target) != ERROR_OK)
return ERROR_FAIL;
/* Display this as early as possible to help people who are using
* really slow simulators. */
LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, i, r->xlen[i],
r->misa);
}
初始化的流程如下:
if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
register_read_abstract(target, NULL, GDB_REGNO_S0, 64);
利用Abstract Command試著讀取64-bits的$S0,如果成功,表示該Hart為64位元的,反之就是32位元的!? (註1)register_read_direct(target, &r->misa, GDB_REGNO_MISA);
riscv_init_registers(target)
** (註1) 痾! 那萬一Hart是64位元但是不支援Abstract Command的話勒!? **
終於來到最後這部分啦!!
首先是初始化Trigger的部分:
/* Then we check the number of triggers availiable to each hart. */
riscv_enumerate_triggers(target);
這部分在「Day 13: 了解Trigger Module的神秘面紗(下)~~!」中有提到過,可以參考其中的"# 2.1 初始化流程"
在初始化完成後,就可以將Target進行Resume:
/* Resumes all the harts, so the debugger can later pause them. */
/* TODO: Only do this if the harts were halted to start with. */
riscv_resume_all_harts(target);
target->state = TARGET_RUNNING;
target_set_examined(target);
等待Debugger連上後,OpenOCD會讓Target再次進入Halted的階段,
因此這邊可以將Hart進行Free-Run(Resume)!!
在這系列中,本文先暖身了一下,剖析OpenOCD中如何處理Target初始化的部分,
並利用之後的examine()來偵測必要資訊!
就如同侯捷老師所說的「源碼面前,了無秘密」!
這邊就是要把程式碼塞好、塞滿!!