一只鱼 さんのプロフィールEE小站フォトブログリストその他 ツール ヘルプ

ブログ


10月10日

Mum计划之urjtag、gdbproxy的下载速度优化

 
国庆假期宅在北京,下回放假一定要出去走走。不过也有收获,就是今天这篇文章。
本文的主要内容是如何修改urjtag和gdbproxy的代码,提高Blackfin GDB调试时,代码下载的速度。
本文属于Mum计划,Mum计划的目的是制作一套开源的Blackfin调试和开发解决方案。
Mum计划使用基于FT2232的JTAG电缆——MumJTAG进行Blackfin处理器的GDB调试。
MumJTAG使用urjtag和gdbproxy来进行GDB调试:urjtag和gdbproxy的基本编译请看这篇文章:http://xianzilu.spaces.live.com/blog/cns!4201FDC93932DDAF!850.entry
本文隐含性的提到JTAG调试原理知识,如果不明白,请自行查找文章。
本文参考文档有,http://blackfin.uclinux.org上的这两篇:
还有http://www.hjtag.com的Twentyone大侠写的一篇,我给放到EE小站的SkyDrive了:
关于DCC下载,请看http://blackfin.uclinux.org上的这篇:
以及ADI官方的Blackfin手册,我下载下来,放到EE小站的SkyDrive:
下面开始正题,转载请注明来自我是一只鱼的EE小站,邮件cosine@126.com
 
虽说之前我成功编译了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

今天就写到这里。过一段时间我会放出整理测试后的源代码以及可执行文件,如果你觉得修改过程太过复杂,可以直接使用我写的代码。