国庆假期宅在北京,下回放假一定要出去走走。不过也有收获,就是今天这篇文章。
本文的主要内容是如何修改urjtag和gdbproxy的代码,提高Blackfin GDB调试时,代码下载的速度。
本文属于Mum计划,Mum计划的目的是制作一套开源的Blackfin调试和开发解决方案。
Mum计划使用基于FT2232的JTAG电缆——MumJTAG进行Blackfin处理器的GDB调试。
本文隐含性的提到JTAG调试原理知识,如果不明白,请自行查找文章。
以及ADI官方的Blackfin手册,我下载下来,放到EE小站的SkyDrive:
虽说之前我成功编译了Blackfin的GDB调试代理,但用起来可不像OpenOCD那么好用,尤其是gdbproxy的代码下载速度仅有4kb/s,一个u-boot就要一分钟才能下载完,简直无法接受。为了让Mum计划的调试方案实用,我付出了很多努力。虽然我没有深究Blackfin JTAG调试原理,但我的代码勉强让gdbproxy的代码下载速度提高到了40~60kb/s,虽然比不上有人专门研究的可以达到120kb/s的ICEBear(
http://www.section5.ch/icebear),但是作为开源调试器,60kb/s的下载速度应该可以对付大多数应用了,我就偷点懒,不继续优化,就让ICEBear有点钱赚。

同时,由于我是囫囵吞枣式的做了这个优化,肯定有很多不合理之处,请真正牛叉的大侠予以指正,谢谢。
为什么urjtag和gdbproxy那么慢呢?因为FT2232这种器件仅仅是一个信号转换器,所有的JTAG操作都是用程序模拟的。FT2232是USB的,这就带来一个问题,USB数据传输不是实时的,每个设备都占用一定的轮询时间;如果要从JTAG读取某些东西,就必须以轮询周期为单位读取。例如,gdbproxy每向JTAG写4个字节(unsigned long),就要读取JTAG状态,看是不是CPU准备好了。由于USB的轮询周期较长,说实话我也不记得了,也许是125us,也许是1ms,那么这个周期写一下数据,下个周期读一下状态,下载速度就只能是个位数的kb了。
说说我是怎么做到的:
1.将默认JTAG TCK频率提高到6MHz;
2.实现传说中的DCC下载功能;
3.关闭大于4字节批量写操作的Cache清空功能。
第一点没有什么说到的,很好理解。
第二点,什么是DCC下载?DCC这个名字其实借用了ARM公司的说法(Debug Communication Channel),ADI有别的叫法,不管怎么叫,他们描述的事实是一致的——CPU和JTAG扫描链共享某个寄存器。说说DCC下载怎么工作吧,CPU运行一段小程序,不断扫描JTAG共享的寄存器里是不是有新数据,如果有,就保存到内存中;JTAG电缆不断通过JTAG口,向JTAG共享寄存器里写入新数据。由于现代CPU的读取和保存数据速度一般都比JTAG扫描链快很多,所以JTAG电缆就不需要看CPU是不是准备好了,节约了读取CPU状态的时间。这里还牵涉到两个问题:(1)如果不使用DCC下载,是什么样子?(2)为什么节约读取CPU状态的时间就能提高速度?问题(1),不使用DCC下载,CPU的JTAG调试逻辑里一般都有一个仿真指令寄存器(Emulation Instruction Register),用JTAG可以向这个寄存器里写入单条处理器指令,例如对于ARM,可以是MOV R0, 0x00之类的,并执行,这样,R0的数值就变成0了,再写入STR R1, R0之类的指令,就把数据存到内存去了,但对于Blackfin,这样需要查询CPU是不是完成了仿真指令。问题(2),刚才提到了,USB的轮询周期时间太长,FT2232内部有FIFO,可以储存4k个的JTAG信号电平变换的时序数据,也就是说,如果不接收,只发送,那么每当JTAG电平数据达到4k个的时候才需要发送一次,这样一次发送就大约可以发送几十个字节,而不是原先的4个字节;再说接收造成的影响,由于每个USB的轮询周期,数据既有读,又有写,如果某个周期必须读一次数据,而写数据没有那么多,就造成了FT2232内部FIFO不能被填满,浪费了时间。呵呵,很复杂。
DCC下载的使用是有代价的——需要一个很小的内存空间来跑CPU DCC扫描程序。也就是说,先通过JTAG下载一段小程序到CPU的SRAM,然后运行这段小程序来下载代码。在我这个优化里,我使用了0xffa08000~0xffa08020这段32字节的空间,这段空间BF531~533都有。我写的CPU DCC扫描程序很简单:
P1.L = LO(DBGSTAT);
P1.H = HI(DBGSTAT);
R1.L = 0x0002;
R1.H = 0x0000;
.loop_dbgstat:
R0 = [P1];
R0 = R0 & R1;
cc = R0 == R1;
if cc jump .write_emudat;
jump .loop_dbgstat;
.write_emudat:
R0 = EMUDAT;
[P0++] = R0;
jump .loop_dbgstat;
需要做的就是启动DCC之前,保护R0、R1、P0、P1和PC,并把R0设成要下载数据的首地址;在执行程序之后,还原被保护的寄存器。
第三点,我是自作主张的,因为我觉得一般执行load命令的时候才会有大量的数据写入,而load命令执行之后,本身程序就要重启,跟cache没有什么关系,所以无所谓。
原理说明白了,下面贴上程序:
- urjtag/src/bfin/bfin.c,982行,增加函数
void
part_emudat_set_new (chain_t *chain, int n, uint32_t value, int exit)
{
part_t *part;
tap_register *r;
assert (exit == EXITMODE_UPDATE || exit == EXITMODE_IDLE);
if (part_scan_select (chain, n, EMUDAT_SCAN) < 0)
return;
part = chain->parts->parts[n];
r = part->active_instruction->data_register->in;
emudat_init_value (r, value);
chain_shift_data_registers_mode (chain, 0, 1, exit);
}
- gdbproxy/target_bfin_new.c,464行
static char default_jtag_connect[] = "cable gnICE";
修改为
static char default_jtag_connect[] = "cable USB-to-JTAG-IF ftd2xx-mpsse 0725:6010";
static char default_jtag_frequency[] = "frequency 6000000";
- gdbproxy/target_bfin_new.c,1258行,增加函数
static void
core_emudat_set_new (int core, uint32_t value, int runtest)
{
part_emudat_set_new (cpu->chain, core, value,
runtest ? EXITMODE_IDLE : EXITMODE_UPDATE);
}
- gdbproxy/target_bfin_new.c,3085行,函数修改为
static void
cache_flush (int core, uint32_t addr, int size)
{
uint32_t p0;
int i;
assert (size > 0);
if (size <= 4)
{
p0 = core_register_get (core, REG_P0);
core_register_set (core, REG_P0, addr);
core_dbgctl_bit_set_emuirlpsz_2 (core, UPDATE);
for (i = (size + addr % CACHE_LINE_BYTES - 1) / CACHE_LINE_BYTES + 1;
i > 0; i--)
core_emuir_set_2 (core, gen_flush (REG_P0),
gen_iflush_pm (REG_P0), RUNTEST);
core_dbgctl_bit_clear_emuirlpsz_2 (core, UPDATE);
core_register_set (core, REG_P0, p0);
}
- gdbproxy/target_bfin_new.c,3409行,函数修改为
static int
memory_write (int core, uint32_t addr, uint8_t *buf, int size)
{
uint32_t p0, r0, p1, r1, pc;
int clobbered[BFIN_DCPLB_NUM];
int i;
uint8_t isram_backup[32];
assert (size > 0);
p0 = core_register_get (core, REG_P0); //保护各个寄存器
p1 = core_register_get (core, REG_P1);
r0 = core_register_get (core, REG_R0);
r1 = core_register_get (core, REG_R1);
pc = core_register_get (core, REG_RETE);
for (i = 0; i < BFIN_DCPLB_NUM; i++)
clobbered[i] = 0;
dcplb_validate_clobber_p0r0 (core, addr, size, clobbered);
core_register_set (core, REG_P0, addr);
core_dbgctl_bit_set_emuirlpsz_2 (core, UPDATE);
if ((addr & 0x3) != 0)
core_emuir_set_2 (core,
gen_move (REG_R0, REG_EMUDAT),
gen_store8pi (REG_P0, REG_R0), UPDATE);
while ((addr & 0x3) != 0 && size != 0)
{
core_emudat_set (core, *buf++, RUNTEST);
addr++;
size--;
}
if (size == 0)
goto finish_write;
if (size >= 4)
{
bfin_resume_from_addr(0, 0, 0xffa08000); //启动已经储存在0xffa08000的CPU DCC扫描程序
for (; size >= 4; size -= 4)
{
uint32_t data;
data = *buf++;
data |= (*buf++) << 8;
data |= (*buf++) << 16;
data |= (*buf++) << 24;
core_emudat_set_new (core, data, RUNTEST); //往共享寄存器里写数据
}
bfin_stop(); //停止CPU DCC扫描程序
}
if (size == 0)
goto finish_write;
core_emuir_set_2 (core,
gen_move (REG_R0, REG_EMUDAT),
gen_store8pi (REG_P0, REG_R0), UPDATE);
for (; size > 0; size--)
core_emudat_set (core, *buf++, RUNTEST);
finish_write:
core_dbgctl_bit_clear_emuirlpsz_2 (core, UPDATE);
dcplb_restore_clobber_p0r0 (core, clobbered);
core_register_set (core, REG_P0, p0); //恢复各个寄存器
core_register_set (core, REG_P1, p1);
core_register_set (core, REG_R0, r0);
core_register_set (core, REG_R1, r1);
core_register_set (core, REG_RETE, pc);
return 0;
}
- gdbproxy/target_bfin_new.c,4318行,增加:
char *freq_string = default_jtag_frequency;
- gdbproxy/target_bfin_new.c,4342行,增加:
{"freq", required_argument, 0, 18},
- gdbproxy/target_bfin_new.c,4520行,增加:
case 18:
strcpy(freq_string, "frequency ");
strcat(freq_string, optarg);
break;
- gdbproxy/target_bfin_new.c,4569行,增加:
jtag_parse_line (chain, freq_string);
- gdbproxy/target_bfin_new.c,5061行,增加:
uint32_t dcc_code[] =
{
0x5008e109,
0xffe0e149,
0x0002e101,
0x0000e141,
0x54089108,
0x18020808,
0x31c72ffc,
0x2ff99200,
}; //CPU DCC扫描程序机器码
bfin_write_mem(0xffa08000, (uint8_t *) dcc_code, 0x20);
还有一个值得说的是,http://blackfin.uclinux.org上最新的urjtag源码中使用了strcasestr函数,这个函数在cygwin的gcc的glibc里面没有,编译无法通过。不过没有关系,可以自己写一个。
- jtag/src/cmd/bfin.c,53行,增加:
#include <ctype.h>
#ifndef HAVE_STRCASESTR
char * strcasestr (char *haystack, char *needle)
{
char *p, *startn = 0, *np = 0;
for (p = haystack; *p; p++) {
if (np) {
if (toupper(*p) == toupper(*np)) {
if (!*++np)
return startn;
} else
np = 0;
} else if (toupper(*p) == toupper(*needle)) {
np = needle + 1;
startn = p;
}
}
return 0;
}
#endif
今天就写到这里。过一段时间我会放出整理测试后的源代码以及可执行文件,如果你觉得修改过程太过复杂,可以直接使用我写的代码。