最近在搞路由器的時(shí)候,不小心把CFE給刷掛了,然后發(fā)現(xiàn)能通過jtag進(jìn)行救磚,所以就對(duì)jtag進(jìn)行了一波研究。
成都創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的富拉爾基網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!最開始只是想救磚,并沒有想深入研究的想法。
變磚的路由器型號(hào)為:LinkSys wrt54g v8
CPU 型號(hào)為:BCM5354
Flash型號(hào)為:K8D6316UBM
首先通過jtagulator得到了設(shè)備上jtag接口的順序。
正好公司有一個(gè)jlink,但是參試了一波失敗,識(shí)別不了設(shè)備。
隨后通過Google搜到發(fā)現(xiàn)了一個(gè)工具叫: tjtag-pi
可以通樹莓派來控制jtag,隨后學(xué)習(xí)了一波樹莓派的操作。
我使用的是rpi3,其接口編號(hào)圖如下:
或者在樹莓派3中可以使用
gpio readall
查看各個(gè)接口的狀態(tài):
rpi3中的Python有一個(gè)
RPi.GPIO
模塊,可以控制這些接口。
舉個(gè)例子:
>>> from RPi import GPIO >>> GPIO.setmode(GPIO.BCM)>>> GPIO.setup(2, GPIO.OUT)>>> GPIO.setup(3, GPIO.IN)
首先是需要進(jìn)行初始化GPIO的模式,BCM模式對(duì)應(yīng)的針腳排序是上面圖中橙色的部門。
然后可以對(duì)各個(gè)針腳進(jìn)行單獨(dú)設(shè)置,比如上圖中,把2號(hào)針腳設(shè)置為輸出,3號(hào)針腳設(shè)置為輸入。
>>> GPIO.output(2, 1)>>> GPIO.output(2, 0)
使用output函數(shù)進(jìn)行二進(jìn)制輸出
>>> GPIO.input(3)1
使用input函數(shù)獲取針腳的輸入。
我們可以用線把兩個(gè)針腳連起來測試上面的代碼。
將樹莓派對(duì)應(yīng)針腳和路由器的連起來以后,可以運(yùn)行tjtag-pi程序。但是在運(yùn)行的過程中卻遇到了問題,經(jīng)常會(huì)卡在寫flash的時(shí)候。通過調(diào)整配置,有時(shí)是可以寫成功的,但是CFE并沒有被救回來,備份flash的數(shù)據(jù),發(fā)現(xiàn)并沒有成功寫入數(shù)據(jù)。
因?yàn)槭褂幂喿邮?,所以我只能自己嘗試研究和造輪子了。
首先是針腳,我見過的設(shè)備給jtag一般是提供了5 * 2以上的引腳。其中有一般都是接地引腳,另一半只要知道4個(gè)最重要的引腳。
這四個(gè)引腳一般情況下的排序是:
TDI TDO TMS TCK
TDI表示輸入,TDO表示輸出,TMS控制位,TCK時(shí)鐘輸入。
jtag大致架構(gòu)如上圖所示,其中TAP-Controller的架構(gòu)如下圖所示:
根據(jù)上面這兩個(gè)架構(gòu),對(duì)jtag的原理進(jìn)行講解。
jtag的核心是TAP-Controller,通過解析TMS數(shù)據(jù),來決定輸入和輸出的關(guān)系。所以我們先來看看TAP-Controller的架構(gòu)。
從上面的圖中我們可以發(fā)現(xiàn),在任何狀態(tài)下,輸出5次1,都會(huì)回到
TEST LOGIC RESET
狀態(tài)下。所以在使用jtag前,我們先通過TMS端口,發(fā)送5次為1的數(shù)據(jù),jtag的狀態(tài)機(jī)將會(huì)進(jìn)入到RESET的復(fù)原狀態(tài)。
當(dāng)TAP進(jìn)入到
SHIFT-IR
的狀態(tài)時(shí),
Instruction Register
將會(huì)開始接收TDI傳入的數(shù)據(jù),當(dāng)輸入結(jié)束后,進(jìn)入到
UPDATE-IR
狀態(tài)時(shí)將會(huì)解析指令寄存器的值,隨后決定輸出什么數(shù)據(jù)。
SHIFT-DR
則是控制數(shù)據(jù)寄存器,一般是在讀寫數(shù)據(jù)的時(shí)候需要使用。
講到這里,就出現(xiàn)一個(gè)問題了,TMS就一個(gè)端口,jtag如何知道TMS每次輸入的值是多少呢?這個(gè)時(shí)候就需要用到TCK端口了,該端口可以稱為時(shí)鐘指令。當(dāng)TCK從低頻變到高頻時(shí),獲取一比特TMS/TDI輸入,TDO輸出1比特。
比如我們讓TAP進(jìn)行一次復(fù)位操作:
for x in range(5): TCK 0 TMS 1 TCK 1
再比如,我們需要給指令寄存器傳入0b10:
1.復(fù)位
2.進(jìn)入RUN-TEST/IDLE狀態(tài)
TCK 0 TMS 0 TCK 1
3.進(jìn)入SELECT-DR-SCAN狀態(tài)
TCK 0 TMS 1 TCK 1
4.進(jìn)入SELECT-IR-SCAN狀態(tài)
TCK 0 TMS 1 TCK 1
5.進(jìn)入CAPTURE-IR狀態(tài)
TCK 0 TMS 0 TCK 1
6.進(jìn)入SHIFT-IR狀態(tài)
TCK 0 TMS 0 TCK 1
7.輸入0b10
TCK 0 TMS 0 TDI 0 TCK 1 TCK 0 TMS 1 TDI 1 TCK 0
隨后就是進(jìn)入
EXIT-IR -> UPDATE-IR
根據(jù)上面的理論我們就可以通過寫一個(gè)設(shè)置IR的函數(shù):
def clock(tms, tdi): tms = 1 if tms else 0 tdi = 1 if tdi else 0 GPIO.output(TCK, 0) GPIO.output(TMS, tms) GPIO.output(TDI, tdi) GPIO.output(TCK, 1) return GPIO.input(TDO)def reset(): clock(1, 0) clock(1, 0) clock(1, 0) clock(1, 0) clock(1, 0) clock(0, 0)def set_instr(instr): clock(1, 0) clock(1, 0) clock(0, 0) clock(0, 0) for i in range(INSTR_LENGTH): clock(i==(INSTR_LENGTH - 1), (instr>>i)&1) clock(1, 0) clock(0, 0)
把上面的代碼理解清楚后,基本就理解了TAP的邏輯。接下來就是指令的問題了,指令寄存器的長度是多少?指令寄存器的值為多少時(shí)是有意義的?
不同的CPU對(duì)于上面的答案都不一樣,通過我在網(wǎng)上搜索的結(jié)果,每個(gè)CPU應(yīng)該都有一個(gè)bsd(boundary scan description)文件。本篇文章研究的CPU型號(hào)是
BCM5354
,但是我并沒有在網(wǎng)上找到該型號(hào)CPU的bsd文件。我只能找了一個(gè)相同廠商不同型號(hào)的CPU的bsd文件進(jìn)行參考。
bcm53101m.bsd
在該文件中我們能看到j(luò)tag端口在cpu端口的位置:
"tck : B46 , " & "tdi : A57 , " & "tdo : B47 , " & "tms : A58 , " & "trst_b : A59 , " & attribute TAP_SCAN_RESET of trst_b : signal is true; attribute TAP_SCAN_IN of tdi : signal is true; attribute TAP_SCAN_MODE of tms : signal is true; attribute TAP_SCAN_OUT of tdo : signal is true; attribute TAP_SCAN_CLOCK of tck : signal is (2.5000000000000000000e+07, BOTH);
能找到指令長度的定義:
attribute INSTRUCTION_LENGTH of top: entity is 32;
能找到指令寄存器的有效值:
attribute INSTRUCTION_OPCODE of top: entity is "IDCODE (11111111111111111111111111111110)," & "BYPASS (00000000000000000000000000000000, 11111111111111111111111111111111)," & "EXTEST (11111111111111111111111111101000)," & "SAMPLE (11111111111111111111111111111000)," & "PRELOAD (11111111111111111111111111111000)," & "HIGHZ (11111111111111111111111111001111)," & "CLAMP (11111111111111111111111111101111) " ;
當(dāng)指令寄存器的值為
IDCODE
的時(shí)候,IDCODE寄存器的輸出通道開啟,我們來看看IDCODE寄存器:
attribute IDCODE_REGISTER of top: entity is "0000" & -- version "0000000011011111" & -- part number "00101111111" & -- manufacturer's identity "1"; -- required by 1149.1
從這里我們能看出IDCODE寄存器的固定輸出為:
0b00000000000011011111001011111111
那我們?cè)趺传@取TDO的輸出呢?這個(gè)時(shí)候數(shù)據(jù)寄存器DR就發(fā)揮作用了。
用代碼形式的表示如下:
def ReadWriteData(data): out_data = 0 clock(1, 0) clock(0, 0) clock(0, 0) for i in range(32): out_bit = clock((i == 31), ((data >> i) & 1)) out_data = out_data | (out_bit << i) clock(1,0) clock(0,0) return out_datadef ReadData(): return ReadWriteData(0)def WriteData(data): ReadWriteData(data)def idcode(): set_instr(INSTR_IDCODE) print(hex(self.ReadData()))
因?yàn)槲乙彩莻€(gè)初學(xué)者,邊界掃描描述文件中的內(nèi)容并不是都能看得懂,比如在邊界掃描文件中并不能看出BYPASS指令是做什么的。但是在其他文檔中,得知BYPASS寄存器一般是用來做測試的,在該寄存器中,輸入和輸出是直連,可以通過比較輸入和輸出的值,來判斷端口是否連接正確。
另外還有邊界掃描寄存器一大堆數(shù)據(jù),也沒完全研究透,相關(guān)的資料少的可憐。而且也找不到對(duì)應(yīng)CPU的文檔。
當(dāng)研究到這里的時(shí)候,我只了解了jtag的基本原理,只會(huì)使用兩個(gè)基本的指令(IDCODE, BYPASS)。但是對(duì)我修磚沒任何幫助。
沒辦法,我又回頭來看tjtag的源碼,在tjtag中定義了幾個(gè)指令寄存器的OPCODE:
INSTR_ADDRESS = 0x08INSTR_DATA = 0x09INSTR_CONTROL = 0x0A
照抄著tjtag中flash AMD的操作,可以成功對(duì)flash進(jìn)行擦除,寫入操作讀取操作。但是卻不知其原理。
這里分享下我的腳本: jtag.py
flash 文檔: https://www.dataman.com/media/datasheet/Samsung/K8D6x16UTM_K8D6x16UBM_rev16.pdf
接下來將會(huì)對(duì)該flash 文檔進(jìn)行研究,并在之后的文章中分享我后續(xù)的研究成果。