前面我们介绍了Jtag的基本内容,以及RISCV的DM操作数据流,基于以上我们现在开始就可以来实现RISCV的DM底层操作的驱动了。
https://mp.weixin.qq.com/s/sx5uXW33MN3ELsxT-7TEZg
https://mp.weixin.qq.com/s/LJiMtDxwkwhXkywKlOoOrw?token=565259220&lang=zh_CN
首先需要实现Jtag的操作, Jtag的时序参考前文介绍。Jtag的操作简单来说实际上就两个,一个是操作IR,一个是操作DR。所以底层接口实现也只需要提供这两个接口即可。
我这里考虑代码的可移植性,将Jtag的代码独立出来,只需要实现对应的IO操作即可移植。
Jtag.c/h完全可移植,Jtag_port.c/h实现IO操作接口。
JtagPport.h如下
extern "C" {
void jtag_port_io_init(void);
uint8_t jtag_port_get_tdo(void);
void jtag_port_set_tdi(uint8_t tdi);
void jtag_port_set_tck(void);
void jtag_port_clr_tck(void);
void jtag_port_set_tms(void);
void jtag_port_clr_tms(void);
}
Jtag_port.c中实现对应的jtag_port_xxx接口。
这些接口后续优化速度时可以考虑再优化,比如使用汇编,使用宏替代函数调用,如果硬件支持IO的SET,CLR则可以直接SET,CLR而不需要读-修改-写的操作。
Jtag.h如下
extern "C" {
void jtag_reset_idle(void);
void jtag_ir(uint32_t ir, int tckcnt);
void jtag_dr(uint8_t* idr, uint8_t* odr, int tckcnt);
void jtag_idle(int tckcnt);
}
Jtag.c实现如下
其中实现进入idle状态
/*
* \fn jtag_reset_idle
* 复位到IDLE状态
* 任意状态保持TMS=1大于等于5个周期,进入RESET状态
* 然后一个周期TMS=0,进入IDLE状态
*/
void jtag_reset_idle(void)
{
int clk;
JTAG_TMS_SET(); /* TMS=1 */
/* 保持TMS=1 7个周期,确保进入RESET */
for(clk = 0; clk < 7; clk++){
JTAG_TCK_CLR(); /* 时钟默认为低,低部分 */
JTAG_IO_DELAY();
JTAG_TCK_SET(); /* 高部分 */
JTAG_IO_DELAY();
}
/* TMS=0 进入IDLE*/
JTAG_TMS_CLR();
JTAG_TCK_CLR();
JTAG_IO_DELAY();
JTAG_TCK_SET();
JTAG_IO_DELAY();
}
操作之间的idle,在DM操作之间,按照规范需要等待指定的idle个时钟
void jtag_idle(int tckcnt)
{
JTAG_TMS_CLR(); /* TMS=0 Idle */
while(tckcnt--){
JTAG_CYCLE_TCK();
}
}
不管时ir还是dr核心都是,tdi移入数据,tdo移出数据,如下
/**
* \fn jtag_Sequence
* TDI和TDO移入移出数据
* \param[in] tckcnt 需要移入移出的位数
* \param[in] tms 需要保持TMS的状态,1保持高,0保持低
* \param[in] tdi 需要TDI移入的数据
* \param[out] tdo 保存TDO移出的数据,可以为空
* \param[out] exit 1:最后一位执行EXIT1_DR/IR 0最后一位不执行EXIT1_DR/IR
*/
static void jtag_sequence (uint32_t tckcnt, int tms, const uint8_t *tdi, uint8_t *tdo, int exit) {
uint8_t ibyte;
uint8_t obyte;
uint8_t ibit;
uint8_t obit;
uint32_t k;
if (0 == tms) {
JTAG_TMS_CLR();
} else {
JTAG_TMS_SET();
}
while (tckcnt) {
if(tdi != 0){
ibyte = *tdi++;
}else{
ibyte = 0xFF;
}
obyte = 0U;
for (k = 8U; k && tckcnt; k--, tckcnt--) {
/*
* tdi移入一位
* tdo移出一位
*/
if((exit !=0) && (tckcnt == 1)){
JTAG_TMS_SET(); /* TMS=1 Set last DR/IR bit & Exit1-DR/IR */
}
ibit = ibyte & 0x01;
JTAG_TDI_OUT(ibit);
JTAG_TCK_CLR();
JTAG_IO_DELAY();
obit = JTAG_TDO_IN();
JTAG_TCK_SET();
JTAG_IO_DELAY();
/* 低位在前 */
ibyte >>= 1;
obyte >>= 1;
obyte |= obit << 7;
}
obyte >>= k; /* 最后不足1字节处理 */
if (tdo != (uint8_t*)0) {
*tdo++ = (uint8_t)obyte;
}
}
}
Ir的操作如下
/**
* \fn jtag_ir
* 选择IR并更新IR的值
* 假设当前位于IDLE状态
* \param[in] ir ir的值
* \param[in] tckcnt ir的位数
*/
void jtag_ir(uint32_t ir, int tckcnt){
JTAG_TMS_SET();
JTAG_CYCLE_TCK(); /* TMS=1 Select-DR-Scan */
JTAG_CYCLE_TCK(); /* TMS=1 Select-IR-Scan */
JTAG_TMS_CLR();
JTAG_CYCLE_TCK(); /* TMS=0 Capture-IR */
JTAG_CYCLE_TCK(); /* TMS=0 Shift-IR */
for (; tckcnt>1; tckcnt--) {
JTAG_CYCLE_TDI(ir&0x01); /* TMS=0 Set IR bits (except last) */
ir >>= 1;
}
JTAG_TMS_SET();
JTAG_CYCLE_TDI(ir&0x01); /* TMS=1 Set last IR bit & Exit1-IR */
JTAG_CYCLE_TCK(); /* TMS=1 Update-IR */
JTAG_TMS_CLR();
JTAG_CYCLE_TCK(); /* TMS=0 Idle */
JTAG_TDI_OUT(1U);
}
Dr的操作如下
/**
* \fn jtag_dr
* 选择DR并移入移出DR的值
* 假设当前位于IDLE状态
* \param[in] idr 待移入dr的值
* \param[in] odr 待移出dr的值
* \param[in] tckcnt dr的位数
*/
void jtag_dr(uint8_t* idr, uint8_t* odr, int tckcnt){
uint32_t n;
JTAG_TMS_SET();
JTAG_CYCLE_TCK(); /* TMS=1 Select-DR-Scan */
JTAG_TMS_CLR();
JTAG_CYCLE_TCK(); /* TMS=0 Capture-DR */
JTAG_CYCLE_TCK(); /* TMS=0 Shift-DR */
/* TMS=0 Set DR bits (except last) */
jtag_sequence (tckcnt, 0, idr, odr, 1); /* TMS=1 Set last DR bit & Exit1-DR */
JTAG_CYCLE_TCK(); /* TMS=1 Update-DR */
JTAG_TMS_CLR();
JTAG_CYCLE_TCK(); /* TMS=0 Idle */
JTAG_TDI_OUT(1U);
}
其中ir和dr的移入移出时需要注意,第一个进入shift状态时的CLK不是数据,后一个才是,最后EXIT1时是最后一位数据。
这里都是使用低位在前,默认CLK低,下降沿输出数据,上升沿采样。
后续也可以考虑上述参数可配置。
为了方便还定义了一些基本操作宏
JTAG_TCK_CLR(); \
JTAG_IO_DELAY(); \
JTAG_TCK_SET(); \
JTAG_IO_DELAY()
JTAG_TDI_OUT(tdi); \
JTAG_TCK_CLR(); \
JTAG_IO_DELAY(); \
JTAG_TCK_SET(); \
JTAG_IO_DELAY()
以上ir和dr操作对照以下流程来即可。
数据流参考前文。 简单来说就是选择ir寄存器,然后操作dr。
这里实现见riscv_debug.c/h,依赖jtag.c/h完全可移植。
Riscv_debug.h如下,即对应riscv dm的几个寄存器的操作,其中ir=0x01时dr表示IDCODE,ir=0x10时dr表示dtmcs,ir=0x11时dr表示dmi
extern "C" {
typedef struct{
uint32_t address;
uint32_t data;
uint8_t op;
uint8_t abits;
}riscv_debug_dmi_st;
void riscv_debug_reset(void);
uint32_t riscv_debug_get_idcode(void);
uint32_t riscv_debug_get_dtmcs(void);
void riscv_debug_dtmhardreset(void);
void riscv_debug_dmireset(void);
void riscv_debug_dmi(riscv_debug_dmi_st* in, riscv_debug_dmi_st* out, int idle);
}
riscv_debug.c实现如下,寄存器的定义可以参考riscv debug的规格书,这里实际上不需要每次都重新选择ir,只需要ir变化时才需要重新选择,这样可以优化速度,这里后面优化速度时再修改。
void riscv_debug_reset(void)
{
jtag_reset_idle();
}
/**
* 31 28 |27 12 |11 1 | 0
* Version |PartNumber |ManufId | 1
* 4 | 16 |11 | 1
*/
uint32_t riscv_debug_get_idcode(void)
{
uint32_t idcode;
jtag_ir(RISCV_DEBUG_IDCODE, 5);
jtag_dr(0, (uint8_t*)&idcode, RISCV_DEBUG_IDCODE_BITS);
return idcode;
}
/**
* 31 21 | 20 18 | 17 | 16 | 15 |14 12 | 11 10 | 9 4 | 3 0
* 0 | errinfo | dtmhardreset| dmireset| 0 | idle | dmistat | abits | version
* 11 | 3 | 1 | 1 | 1 | 3 | 2 | 6 | 4
*/
uint32_t riscv_debug_get_dtmcs(void)
{
uint32_t dtmcs;
jtag_ir(RISCV_DEBUG_DTMCS, 5);
jtag_dr(0, (uint8_t*)&dtmcs, RISCV_DEBUG_DTMCS_BITS);
return dtmcs;
}
void riscv_debug_dtmhardreset(void)
{
uint32_t dtmcs = 1u<<17;
jtag_ir(RISCV_DEBUG_DTMCS, 5);
jtag_dr((uint8_t*)&dtmcs, 0,RISCV_DEBUG_DTMCS_BITS);
return dtmcs;
}
void riscv_debug_dmireset(void)
{
uint32_t dtmcs = 1u<<16;
jtag_ir(RISCV_DEBUG_DTMCS, 5);
jtag_dr((uint8_t*)&dtmcs, 0,RISCV_DEBUG_DTMCS_BITS);
return dtmcs;
}
/**
* abits+33 34 | 33 2 | 1 0
* address | data | op
* abits | 32 | 2
* | 0(nop) 0(success)
* | 1(read) 1(reserved)
* | 2(write) 2(failed)
* | 3(reserved) 3(busy)
*/
void riscv_debug_dmi(riscv_debug_dmi_st* in, riscv_debug_dmi_st* out, int idle)
{
uint64_t tdi = 0;
uint64_t tdo = 0;
tdi = (uint64_t)in->op & 0x03;
tdi |= (uint64_t)(in->data)<<2;
tdi |= (uint64_t)(in->address)<<34;
jtag_ir(RISCV_DEBUG_DMI, 5);
jtag_dr((uint8_t*)&tdi, (uint8_t*)&tdo, in->abits + 34);
jtag_idle(idle);
out->op = tdo & 0x03;
out->data = (tdo >>2) & 0xFFFFFFFF;
out->address = (tdo >>34);
}
这里在shell中实现一下命令,shell实现可以参考公众号前面对应的文章。
这里实现了以下命令
static void riscvdebugresetfunc(uint8_t* param);
static void riscvdebugidcodefunc(uint8_t* param);
static void riscvdebugdtmcsfunc(uint8_t* param);
static void riscvdebugdtmhardresetfunc(uint8_t* param);
static void riscvdebugdmiresetfunc(uint8_t* param);
static void riscvdebugdmifunc(uint8_t* param);
{ (uint8_t*)"rvd_reset", riscvdebugresetfunc, (uint8_t*)"rvd_reset"},
{ (uint8_t*)"rvd_idcode", riscvdebugidcodefunc, (uint8_t*)"rvd_idcode"},
{ (uint8_t*)"rvd_dtmcs", riscvdebugdtmcsfunc, (uint8_t*)"rvd_dtmcs"},
{ (uint8_t*)"rvd_dtmhardreset", riscvdebugdtmhardresetfunc, (uint8_t*)"rvd_dtmhardreset"},
{ (uint8_t*)"rvd_dmireset", riscvdebugdmiresetfunc, (uint8_t*)"rvd_dmireset"},
{ (uint8_t*)"rvd_dmi", riscvdebugdmifunc, (uint8_t*)"rvd_dmi abits address[hex] data[hex] op idle"},
static void riscvdebugresetfunc(uint8_t* param)
{
(void)param;
riscv_debug_reset();
}
static void riscvdebugidcodefunc(uint8_t* param)
{
(void)param;
uint32_t idcode = riscv_debug_get_idcode();
xprintf("idcode:0x%x\r\n",idcode);
}
static void riscvdebugdtmcsfunc(uint8_t* param)
{
(void)param;
uint32_t dtmcs = riscv_debug_get_dtmcs();
xprintf("dtmcs:0x%x\r\n",dtmcs);
}
static void riscvdebugdtmhardresetfunc(uint8_t* param)
{
(void)param;
riscv_debug_dtmhardreset();
}
static void riscvdebugdmiresetfunc(uint8_t* param)
{
(void)param;
riscv_debug_dmireset();
}
static void riscvdebugdmifunc(uint8_t* param)
{
int abits;
uint32_t addr;
uint32_t data;
int op;
int idle;
riscv_debug_dmi_st in;
riscv_debug_dmi_st out;
if(5 == sscanf((const char*)param, "%*s %d %lx %lx %d %d", &abits, &addr, &data, &op, &idle))
{
xprintf("[in]:\r\n");
in.abits = abits;
in.address = addr;
in.data = data;
in.op = op;
xprintf("abits:%d,addr:0x%x,data:0x%x,op:%d,idle:%d\r\n",abits,addr,data,op,idle);
riscv_debug_dmi(&in, &out, idle);
xprintf("[out]:\r\n");
xprintf("addr:0x%x,data:0x%x,op:%d\r\n",out.address,out.data,out.op);
}
}
然后注意测试上述命令,用逻辑分析仪住区对应的波形。
rvd_dmireset
以0x10 dmcontrol寄存器为例
需要先写bit0为0,查询等待该位为0
再写1,查询等待该位位1
WRITE-NOP-READ-NOP
写0
Nop查看
读0x10
此时还读不到值,需要nop一次回读值,看到确实变为了0
WRITE-NOP-READ-NOP
先写1
再NOP
再READ
再NOP,此时可以看到bit1置位为了1
以上实现了riacv debug的jtag底层操作接口,驱动分为两层,最底层是jtag的操作接口,移植对应的io操作即可,主要是ir,dr的操作,这个是jtag标准,所以也可以移植适用于任何其他使用jtag的地方。然后在此之上,实现了riscv debug中规定的idcode,dtmcs和dmi操作的接口,基于此就可以实现任意的riscv debug操作了。
理论上基于此底层接口,我们只需要关注应用层,就可以实现我们自己的调试工具了。比如适配OpenOCD,甚至也可以自己做一套上位机调试工具。