關(guān)于openocd JTAG driver開(kāi)發(fā)的學(xué)習(xí)筆記

寫在開(kāi)始

事情開(kāi)始于公司需要對(duì)芯片定制一個(gè)openocd driver. 然后我開(kāi)始了為期兩周的JTAG學(xué)習(xí)之旅.

前提描述

具體的協(xié)議內(nèi)容不是這篇筆記的重點(diǎn), 就不仔細(xì)介紹了. 我把需要的一些要點(diǎn)單獨(dú)拎出來(lái)描述一下.

  • 這次的實(shí)際物理器件是ftdi232h
  • (tms, tdi, tck)或者(tdo)都需要一條單獨(dú)的指令, 每條指令的長(zhǎng)度大約為80個(gè)比特.

關(guān)于這篇筆記的結(jié)構(gòu)

這篇筆記主要是以時(shí)間發(fā)展順序的角度進(jìn)行描述的, 筆記內(nèi)不介紹特別深入的內(nèi)容, 主要以導(dǎo)讀為主, 記錄我理解的要點(diǎn).
因?yàn)楣ぷ鲿r(shí)每一段時(shí)間都有一個(gè)工作重心, 所以我在整理筆記時(shí)根據(jù)當(dāng)時(shí)的工作重心取了不同的副標(biāo)題.

在這次的開(kāi)發(fā)經(jīng)歷了兩個(gè)階段:
最初, 我是基于bitbang_interface實(shí)現(xiàn)了read, write, reset三個(gè)方法. 這種方式的實(shí)現(xiàn)勝在簡(jiǎn)單, 不需要了解特別多的JTAG協(xié)議細(xì)節(jié).
后來(lái), 由于使用bitbang效率比較低, 所以需要我針對(duì)驅(qū)動(dòng)協(xié)議實(shí)現(xiàn)了一個(gè)特定版本的驅(qū)動(dòng)代碼. 這個(gè)版本要求對(duì)JTAG協(xié)議的細(xì)節(jié)有一定的理解, 不然在開(kāi)發(fā)過(guò)程中無(wú)法進(jìn)行常規(guī)的debug工作.

第一個(gè)階段對(duì)應(yīng)初探JTAG初探openocd.
第二個(gè)階段對(duì)應(yīng)再探JTAG再探openocd.

一些額外的業(yè)務(wù)分析(可以忽略)

這次協(xié)議慢的原因有兩個(gè):

  1. 驅(qū)動(dòng)協(xié)議效率本身比較低
    簡(jiǎn)單描述一下這次所需要的驅(qū)動(dòng)協(xié)議的效率.
    假如是一個(gè)直接支持ftdi mpsse實(shí)現(xiàn)JTAG的: 它的一次tdi/tdo都只需要填充一個(gè)單獨(dú)的bit(mpsse會(huì)直接進(jìn)行tck和tdo的處理).
    而對(duì)于我所需要實(shí)現(xiàn)的協(xié)議而言, 一次發(fā)數(shù)據(jù)的操作需要一次tck的翻轉(zhuǎn)(兩個(gè)寫命令), 一次讀數(shù)據(jù)的操作需要額外加入一次讀操作(即為寫,讀,寫三次命令)
    所以對(duì)于不同的請(qǐng)求, 我協(xié)議本身的損耗是ftdi的160倍(寫操作)和240倍(讀操作).

  2. usb請(qǐng)求導(dǎo)致的效率低下
    雖然協(xié)議本身就很慢, 但是真正拖慢運(yùn)行效率的實(shí)際上是bitbang的接口本身.
    使用bitbang慢的原因和協(xié)議本身慢的原因不太一樣. 這個(gè)瓶頸發(fā)生在每一次bitbang的讀寫操作都需要發(fā)起一次新的usb請(qǐng)求, 然后等待響應(yīng).
    mpsse支持很大批量的連續(xù)讀寫操作, 這是為什么我可以棄用bitbang而自己實(shí)現(xiàn)的原因.

如果對(duì)于ftdi或者mpsse有興趣的可以查詢ftdi的官網(wǎng)

初探JTAG: JTAG是什么?

在以前的開(kāi)發(fā)過(guò)程中, 用過(guò)JTAG, 但是我并不知道JTAG具體是什么. 所以在任務(wù)開(kāi)始的第一個(gè)階段我主要是搜羅資料并建立對(duì)JTAG的直觀印象.

  1. 參考資料
    下面是我在youtube(不可描述的)網(wǎng)站上搜到視頻資料, 視頻主要是對(duì)JTAG進(jìn)行了一些導(dǎo)讀. 視頻的重點(diǎn)主要是關(guān)于JTAG的起源與邊界掃描.

  2. 課代表時(shí)間

  • JTAG主要起源于PCB板級(jí)檢查時(shí)候的boundary scan.
  • 整個(gè)系統(tǒng)的有并行連接的TMS(test mode select) 和 TCK(test clock), 以及串行連接的TDI(test data output), TDO(test data input)組成.
  • JTAG可以直接控制掃描芯片周圍的pin.
  • 通過(guò)JTAG控制器, 還可以與芯片內(nèi)部的設(shè)備進(jìn)行直接通訊.(這就是我們常用gdb over JTAG的實(shí)現(xiàn)方法).
  • JTAG的數(shù)據(jù)輸入和輸出都是有延遲的, 延遲取決于整個(gè)系統(tǒng)中所有寄存器數(shù)目的總和.
JTAG Boundary Scan

邊界檢查的GUI

在上面的截圖中可以看到, 有些JTAG的連接器可以通過(guò)GUI直接將掃描的結(jié)果顯示出來(lái).
例如: 在某個(gè)板子上也許兩個(gè)設(shè)備的pin直接接入了一個(gè)反向器, 如果控制某個(gè)pin輸入/輸出時(shí), 則可以檢查另一個(gè)pin的狀態(tài)是否正確.

初探openocd: openocd是什么? 怎么實(shí)現(xiàn)一個(gè)驅(qū)動(dòng)?

openocd的全稱是Open On-Chip Debugger.
因?yàn)樾枰槍?duì)openocd寫一個(gè)驅(qū)動(dòng), 所以我關(guān)注的重點(diǎn)主要落在了"我要怎么實(shí)現(xiàn)一個(gè)驅(qū)動(dòng)"上.
在查閱了openocd 官方文檔之后, 我大致上對(duì)openocd的使用和開(kāi)發(fā)架構(gòu)有了基本的了解, 大致上知道在哪里找代碼了.

整個(gè)openocd的使用層級(jí)分為三級(jí):

  • interface: 實(shí)現(xiàn)JTAG協(xié)議的內(nèi)容, 這也是我所需要關(guān)心的重點(diǎn).
  • board: 根據(jù)特別的開(kāi)發(fā)板子, 同一個(gè)接口可能也有不同的配置選項(xiàng).
  • target: 芯片支持的特定的JTAG指令, 這些一般是芯片廠商提供. 我這次的任務(wù)的對(duì)象是一個(gè)現(xiàn)有的cpu, 所以這一層我不需要關(guān)心.

簡(jiǎn)述bitbang

bitbang接口的實(shí)現(xiàn)在openocd/src/jtag/drivers/bitbang.c里面, 對(duì)于使用這個(gè)"框架"的用戶而言, 只需要提供三個(gè)方法, 就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的driver.
在我clone下來(lái)的openocd版本中, sysfsgpioep93xx這兩個(gè)驅(qū)動(dòng)都是基于bitbang實(shí)現(xiàn)的.

/* ep93xx的實(shí)現(xiàn)代碼 */
/* 提供一個(gè)全局可見(jiàn)的結(jié)構(gòu)體, 這個(gè)接口填寫的內(nèi)容都是模板化 */
struct jtag_interface ep93xx_interface = {
    .name = "ep93xx",

    .supported = DEBUG_CAP_TMS_SEQ,
    .execute_queue = bitbang_execute_queue, /* bitbang 實(shí)現(xiàn) JTAG命令解析的函數(shù)入口 */
    .init = ep93xx_init,
    .quit = ep93xx_quit,
};

/* 下面是三個(gè)需要用戶實(shí)現(xiàn)的接口 */
static struct bitbang_interface ep93xx_bitbang = {
    .read = ep93xx_read,
    .write = ep93xx_write,
    .reset = ep93xx_reset,
    .blink = 0,
};

關(guān)于如何添加一個(gè)新驅(qū)動(dòng)

當(dāng)實(shí)現(xiàn)了一個(gè)驅(qū)動(dòng)之后, 需要手工的將自己的驅(qū)動(dòng)加入編譯選項(xiàng), openocd使用的是automake, 加入一個(gè)新驅(qū)動(dòng)的編譯選項(xiàng)位置比較多, 所以下面列出需要改動(dòng)的地方, 便于參考.
下面以sysfsgpio的配置文件作為藍(lán)本進(jìn)行介紹.
openocd/configure.ac: 在項(xiàng)目根目錄中加入對(duì)編譯選項(xiàng)的支持

# ... snip ...
AC_ARG_ENABLE([sysfsgpio],
  AS_HELP_STRING([--enable-sysfsgpio], [Enable building support for programming driven via sysfs gpios.]),
  [build_sysfsgpio=$enableval], [build_sysfsgpio=no])

# ... snip ...
# 其中關(guān)于對(duì)bitbang的依賴就是通過(guò) "build_bitbang" 這一行實(shí)現(xiàn)的
AS_IF([test "x$build_sysfsgpio" = "xyes"], [
  build_bitbang=yes
  AC_DEFINE([BUILD_SYSFSGPIO], [1], [1 if you want the SysfsGPIO driver.])
], [
  AC_DEFINE([BUILD_SYSFSGPIO], [0], [0 if you don't want SysfsGPIO driver.])
])

# ... snip ...

AM_CONDITIONAL([SYSFSGPIO], [test "x$build_sysfsgpio" = "xyes"])

openocd/src/jtag/drivers/Makefile.am: 在編譯目錄是加入對(duì)應(yīng)的源文件

if SYSFSGPIO
DRIVERFILES += %D%/sysfsgpio.c
endif

openocd/src/jtag/interfaces.c: 注冊(cè)驅(qū)動(dòng)

/* ... snip ... */
#if BUILD_SYSFSGPIO == 1
extern struct jtag_interface sysfsgpio_interface;
#endif
/* ... snip ... */
struct jtag_interface *jtag_interfaces[] = {
        /* ... snip ... */
#if BUILD_SYSFSGPIO == 1
        &sysfsgpio_interface,
#endif
        /* ... snip ... */
}
/* ... snip ... */

關(guān)于bitbang的性能不足(可略)

在開(kāi)發(fā)和測(cè)試之后發(fā)現(xiàn), bitbang模式下的驅(qū)動(dòng)可以使用, 但是在調(diào)用GDB命令時(shí)有十分明顯的遲鈍感. 所以如果想要提升速度, 偷懶的方法就不好用了, 需要自己全面實(shí)現(xiàn)一個(gè)驅(qū)動(dòng).
由上文可知, bitbang框架的命令解析函數(shù)是bitbang_execute_queue.
下面是bitbang對(duì)于我所需要實(shí)現(xiàn)驅(qū)動(dòng)之所以慢的原因.

int bitbang_execute_queue(void)
{
    while (cmd) {
        switch (cmd->type) {
            case JTAG_SCAN:
                bitbang_end_state(cmd->cmd.scan->end_state);
                /* INFO: 每次都需要?jiǎng)討B(tài)分配一段大空間 */
                scan_size = jtag_build_buffer(cmd->cmd.scan, &buffer);
                type = jtag_scan_type(cmd->cmd.scan);
                if (bitbang_scan(cmd->cmd.scan->ir_scan, type, buffer,
                            scan_size) != ERROR_OK)
                    return ERROR_FAIL;
                if (jtag_read_buffer(buffer, cmd->cmd.scan) != ERROR_OK)
                    retval = ERROR_JTAG_QUEUE_FAILED;
                if (buffer)
                    free(buffer);
                break;
                /* ... snip ...*/
        }
    /* ... snip ...*/
}

static int bitbang_scan(bool ir_scan, enum scan_type type, uint8_t *buffer,
        unsigned scan_size)
{
    /* ... snip ...*/
    size_t buffered = 0;
    /* INFO: 每次讀寫都是單獨(dú)執(zhí)行的, usb的反應(yīng)時(shí)間成為了我驅(qū)動(dòng)的瓶頸 */
    for (bit_cnt = 0; bit_cnt < scan_size; bit_cnt++) {
        /* ... snip ...*/
        if (bitbang_interface->write(0, tms, tdi) != ERROR_OK)
            return ERROR_FAIL;

        if (type != SCAN_OUT) {
            if (bitbang_interface->buf_size) {
                if (bitbang_interface->sample() != ERROR_OK)
                    return ERROR_FAIL;
                buffered++;
            } else {
                switch (bitbang_interface->read()) {
                    case BB_LOW:
                        buffer[bytec] &= ~bcval;
                        break;
                    case BB_HIGH:
                        buffer[bytec] |= bcval;
                        break;
                    default:
                        return ERROR_FAIL;
                }
            }
        }

        if (bitbang_interface->write(1, tms, tdi) != ERROR_OK)
            return ERROR_FAIL;
        /* ... snip ...*/
    }
    /* ... snip ...*/
}

再探JTAG: JTAG的協(xié)議長(zhǎng)什么樣?

由于bitbang的實(shí)現(xiàn)的版本太慢了, 所以需要自己實(shí)現(xiàn)對(duì)于JTAG命令的解析. 在這種情況下, 如果不對(duì)協(xié)議有所了解, 那基本上是無(wú)法進(jìn)行開(kāi)發(fā)和調(diào)試的. 這個(gè)階段我主要是先簡(jiǎn)單讀了一下JTAG的協(xié)議, 其中fpga4fun中的一篇關(guān)于JTAG如何工作的文章很好的解釋了我想關(guān)注的重點(diǎn);

  1. 參考資料
  1. 課代表時(shí)間
JTAG狀態(tài)機(jī)

JTAG寫IR

下面來(lái)簡(jiǎn)要地介紹一下我從參考資料中捕獲的要點(diǎn):

  • 當(dāng)測(cè)試器(驅(qū)動(dòng)程序)控制TMS時(shí), 整個(gè)板子中所有連接上TMS的設(shè)備都同時(shí)進(jìn)入同一種狀態(tài).
  • JTAG有DR(data registers)和IR(instruction registers)兩種寄存器, 通過(guò)對(duì)IR和DR進(jìn)行配置實(shí)現(xiàn)與芯片內(nèi)部JTAG控制器的交互.
  • 每種設(shè)備的IR指令長(zhǎng)度不定, DR寄存器對(duì)于不同指令可能長(zhǎng)度也不一定.
  • 可以把串聯(lián)的多個(gè)設(shè)備的寄存器想象成一個(gè)queue. 當(dāng)芯片處于讀入或者讀出模式時(shí), 需要一次填入或者取出整個(gè)queue中的數(shù)據(jù).
  • 假如一個(gè)板子上的設(shè)備是靜態(tài)的, 那JTAG的命令都是可以直接計(jì)算出來(lái)的, 即可以針對(duì)板子上JTAG的TDO和TDI的順序直接控制對(duì)應(yīng)的器件.
  • BYPASS命令在IEEE中強(qiáng)制要求為全1. 通過(guò)往IR寄存器里面填入遠(yuǎn)大于queue深度的1, 就可以確保所有設(shè)備都處于BYPASS模式.
  • BYPASS模式下, 所有的JTAG設(shè)備的DR的長(zhǎng)都為1, 所以可以通過(guò), 寫入足量的0, 再寫入足量的1, 就可以動(dòng)態(tài)地探測(cè)出線路上有幾個(gè)設(shè)備.
  • IDCODE命令是可選的, 對(duì)于支持IDCODE命令的設(shè)備而言, reset狀態(tài)之后IR的指令就是IDCODE. IDCODE指令下DR的長(zhǎng)度都是固定的.
  • IDCODE帶有JTAG設(shè)備的信息, 即BYPASS+IDCODE兩個(gè)命令合在一起可以實(shí)現(xiàn)自動(dòng)探測(cè)班上JTAG設(shè)備.

再探openocd

關(guān)于jtag_command

其實(shí)通過(guò)上述的JTAG協(xié)議的學(xué)習(xí), 我大致將JTAG命令的類型分為三類: 狀態(tài)移動(dòng)(TMS), 數(shù)據(jù)寫入讀出(TDI/TDO), 控制(RST).
其中數(shù)據(jù)的寫入讀出的命令長(zhǎng)度可能十分的長(zhǎng)(而這個(gè)是我關(guān)注的重點(diǎn)).
openocd中命令類型enum jtag_command_type的定義如下:

enum jtag_command_type {
    JTAG_SCAN         = 1,
    JTAG_TLR_RESET    = 2,
    JTAG_RUNTEST      = 3,
    JTAG_RESET        = 4,
    JTAG_PATHMOVE     = 6,
    JTAG_SLEEP        = 7,
    JTAG_STABLECLOCKS = 8,
    JTAG_TMS          = 9,
};

其中 JTAG_SCAN負(fù)責(zé)數(shù)據(jù)的寫入和讀出, 這是我程序改進(jìn)的關(guān)鍵點(diǎn). 具體的內(nèi)容和業(yè)務(wù)相關(guān)度比較高, 這篇筆記內(nèi)容就不仔細(xì)介紹了.

關(guān)于GDB到openocd到target的內(nèi)容

openocd/src/target目錄下的目標(biāo)需要實(shí)現(xiàn)struct target_type, 向openocd中注冊(cè)了一簇調(diào)試用的方法, 讓用戶可以通過(guò)GDB或者tcl進(jìn)行調(diào)試目標(biāo).
這些方法在底層調(diào)用了openocd/src/jtag/core.c函數(shù)中的方法(例如jtag_add_ir_scan), 實(shí)現(xiàn)了對(duì)驅(qū)動(dòng)的控制.

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,

    .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,
    .hit_watchpoint = riscv_hit_watchpoint,

    .arch_state = riscv_arch_state,

    .run_algorithm = riscv_run_algorithm,

    .commands = riscv_command_handlers
};

參考資料匯總

[1] boundary scan
[2] JTAG概述 youtube
[3] openocd 官方文檔
[4] JTAG spec: IEEE 1149.1
[5] How JTAG Works

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末藏杖,一起剝皮案震驚了整個(gè)濱河市收捣,隨后出現(xiàn)的幾起案子削饵,更是在濱河造成了極大的恐慌尿招,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡箫老,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門黔州,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)耍鬓,“玉大人,你說(shuō)我怎么就攤上這事流妻∩瘢” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵合冀,是天一觀的道長(zhǎng)各薇。 經(jīng)常有香客問(wèn)我项贺,道長(zhǎng)君躺,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任开缎,我火速辦了婚禮棕叫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奕删。我一直安慰自己俺泣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布完残。 她就那樣靜靜地躺著伏钠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谨设。 梳的紋絲不亂的頭發(fā)上熟掂,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音扎拣,去河邊找鬼赴肚。 笑死素跺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的誉券。 我是一名探鬼主播指厌,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踊跟!你這毒婦竟也來(lái)了踩验?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤商玫,失蹤者是張志新(化名)和其女友劉穎晰甚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體决帖,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厕九,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了地回。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扁远。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖刻像,靈堂內(nèi)的尸體忽然破棺而出畅买,到底是詐尸還是另有隱情,我是刑警寧澤细睡,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布谷羞,位于F島的核電站,受9級(jí)特大地震影響溜徙,放射性物質(zhì)發(fā)生泄漏湃缎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一蠢壹、第九天 我趴在偏房一處隱蔽的房頂上張望嗓违。 院中可真熱鬧,春花似錦图贸、人聲如沸蹂季。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)偿洁。三九已至,卻和暖如春沟优,著一層夾襖步出監(jiān)牢的瞬間涕滋,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工净神, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留何吝,地道東北人溉委。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爱榕,于是被迫代替她去往敵國(guó)和親瓣喊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容