Version: 0.1.0
November 2003
Rongkai Zhan (Chinese Name: 詹荣开)
2 The JTAGER Commands Parser
o
2.1 The Data
Struct of JTAGER Commands
o
2.3 The Run
Function of JTAGER Commands
4 The
Target CPU Cores Module
5 The Flash
Interface Module
o
5.2 How to Add
the Support to A New Flash Chip
Index
JTAGER是一个由命令行驱动的程序,它主要由4个模块组成:1)命令解析器(command parser)模块;2)JTAG仿真器接口模块;3)目标机CPU
core接口模块;4)Flash接口模块。图1可以说明这4个模块之间的关系:
+----------------------------------------------+ |
JTAGER Commands Parser | +----------------------------------------------+
|
| |
\|/
| | +------------------------+ | | | Flash
Interface Module |
| | +------------------------+ | |
| | |
\|/ \|/ | +-------------------------------------+ | | Target CPU Cores Interface
Module | | +-------------------------------------+ |
|
|
\|/
\|/ +----------------------------------------------+ | JTAG
Emulators Interface Module | +----------------------------------------------+ Figure 1 The relationships of
the four JTAGER modules |
所有与JTAGER命令解析器实现相关的源代码文件都被保存到目录src/cmd下。JTAGER命令解析器可以分为两大实现部分:1)统一的命令解析器接口;2)具体的JTAGER命令实现。统一的命令解析器接口实现在源文件src/cmd/command.c中,它也包含两部分:一个包含所有JTAGER命令结构指针数组和一个命令调度器(Command Dispatcher),命令调度器是JTAGER命令解析器的唯一入口。
数据结构jtager_cmd_t被用来描述一个JTAGER命令,它定义在include/jtager.h头文件中:
typedef
struct jtager_cmd_struct { const char *name; const char *description; struct option *long_options; /*
the GNU-style long options */ int non_option_args; /* the
number of non-option arguments */ void (*help) (void); int (*run) (int cmd_argc, char *cmd_argv[]); }jtager_cmd_t; List 1 数据结构jtager_cmd_t的定义 |
各数据结构成员的含义如下:
l
字符串指针name:指向该命令的名字字符串。每一个JTAGER命令都必须有一个唯一的名字字符串。
l
指针description:该命令的简单描述字符串。
l
选项指针long_options:指向一个包含该JTAGER命令的命令选项数组。注意:最后一个数组元素必须是{0, 0, 0, 0}。
l
non_option_args:该JTAGER命令的非选项参数个数。
l
函数指针help:指向该JTAGER命令的帮助函数。
l
函数指针run:指向该JTAGER命令的运行函数。命令调度器最终将过这个函数指针来调用特定命令的运行函数。
每一个JTAGER命令都对应有一个jtager_cmd_t类型的变量来描述这个命令,其地址也被登记在jtager_cmd_t类型的指针数组jtager_cmds[]中。这样,我们就可以通过指针数组jtager_cmds[]跳转到任何一个我们想要的JTAGER命令。实现在src/cmd/command.c文件中的函数command_index()以及基于这个函数的宏cmd_index()可以帮助我们通过命令名来检索jtager_cmds[]指针数组。
命令调度器――函数parse_cmdline()实现在源文件src/cmd/command.c中。它是JTAGER命令解析器的唯一入口。同时这个函数也是JTAGER命令解析全过程的总控函数,它最终会根据命令名向下调用指定JTAGER命令的运行函数。正因此,parse_cmdline()函数才被称为“命令调度器”。
命令调度器包含以下4个执行步骤:
l Step 1:分解命令行字符串
这一步将以空格字符为分隔符,把整个命令行字符串分解成多个子字符串数组,并将它们的指针保存在字符串指针数组cmdline_argv[]中,同时子字符串数组的个数也将被保存在变量cmdline_argc中。注意:cmdline_argv[0]总是指向命令行中的JTAGER命令名,因此cmdline_argc的最小值是1,而cmdline_argv[cmdline_argc]总是被设置为NULL。
这一步的目的是模拟C语言main()函数的参数argc和参数argv,从而使得各JTAGER命令的运行函数可以调用glibc函数getopt_long()来解析JTAGER命令选项和参数。
l Step 2:检索相应的JTAGER命令结构变量
经过上一步的分解后,字符串指针cmdline_argv[0]将总是指向命令行中的JTAGER命令名。因此可以以它为参数调用cmd_index宏来在jtager_cmds[]指针数组中进行检索。
l Step 3:检查JTAGER命令选项的合法性
这一步骤通过调用函数match_option_args()来检查当前命令行中的选项和参数是否就是指定JTAGER命令所期望的选项和参数。
这一步骤的需要是因为glibc的getopt_long()函数存在一个丑陋的BUG。例如,假设有这样一个hello程序,它可以接收一个长名字选项--list,其源码如List 2所示:
#include <stdio.h> #include <stdlib.h> #include <getopt.h> static int list_flag = 0; int option_index = 0; struct option long_options[]
= { {"list", no_argument, &list_flag, 1}, {0, 0, 0, 0} }; void main(int argc, char
**argv) { int retval; while (1) { retval = getopt_long(argc, argv,
"l", long_options,
&option_index); if (retval == -1) break; switch (retval) { case 0: break; case 'l': break; case '?': default: printf("Error:
syntax error.\n"); exit(-1); } } if (list_flag) printf("The --list option is
specified.\n"); exit(0); } List 2 hello.c源文件 |
对于这样一个hello程序,如果我们以命令行“hello --li 0x11”,它将仍然打印消息“The
--list option is specified.”,而且它不能识别非法的非选项参数0x11!
l 第四步:调用指定JTAGER命令的运行函数
通过调用指定JTAGER命令的run函数,命令调度器将控制权交给特定JTAGER命令的run函数,由它来对当前命令行做进一步的解析。
JTAGER命令的run函数是JTAGER命令解析器的最后一个过程。JTAGER命令的run函数的最主要任务是:1)调用getopt_long()函数来解析它自己的命令选项、选项参数和非选项参数。2)根据不同的选项执行不同的操作,
这通常需要向下调用Flash接口、Target
CPU Core接口和JTAG接口。
(To be continued)
与目标机的CPU Core接口实现相关的源文件都放在src/target目录中,其中,子目录src/target/arm7tdmi下的源文件实现ARM7TDMI
core,子目录src/target/arm9tdmi下的源文件实现ARM7TDMI core。
数据结构core_register_t被用来描述CPU的core寄存器,比如:ARM
core中的R0、CPSR等寄存器。该数据结构定义在include/target.h头文件中:
typedef
struct core_register_struct { char *name; u32 value; }core_register_t; List 4-1 数据结构core_register_t的定义 |
其中,name成员表示这个寄存器的名字,value成员表示这个寄存器的当前值。
数据结构ice_register_t被用来描述ARM7TDMI或ARM9TDMI
core内的EmbeddedICE-RT logic中的ICE寄存器,它定义在include/target.h头文件中:
typedef
struct ice_register_struct { char *name; char *desc; /* long name string
*/ u32 addr; int bitnr; /* the bit length of
register */ u32 value; }ice_register_t; List 4-2 数据结构ice_register_t的定义 |
各数据成员的含义如下:
l
name:ICE寄存器的名字字符串。
l
desc:完整的ICE寄存器名字字符串。
l
addr:ICE寄存器的地址。
l
bitnr:ICE寄存器的位长度。
l
value:ICE寄存器的当前值。
数据结构scan_chain_t被用来描述目标机CPU的JTAG接口中的一个扫描链,它定义在include/target.h头文件中:
typedef
struct scan_chain_struct { char *name; /* the name of scan
chain */ int bitnr; /* the bit number of
testdata */ /* the test data written in and
the test data read out */ u32
writein[MAX_SCANCHAIN_LENGTH]; u32
readout[MAX_SCANCHAIN_LENGTH]; /* points to the data struct
specific to the scan chain */ void *private; }scan_chain_t; List 4-3 数据结构scan_chain_t的定义 |
各成员的含义如下:
l
name:这个扫描链的名字。
l
bitnr:这个扫描链的位宽度。
l
数组writein[]:上一次写入扫描链的数据值。
l
数组readout[]:上一次从扫描链读出的数组值。
l
指针private:指向这个扫描链的私有数据,它可以是NULL。
我们用数据结构target_t来描述目标机CPU,每种类型的CPU core实现都必须定义一个target_t类型的变量来表示它自己。该数据结构定义在include/target.h头文件中:
typedef
struct target_struct { int type; /* ARM7TDMI or ARM9TDMI */ int mode; /* ARM or Thumb */ int status; /* halt, monitor or
running */ int halt_reason; /*
BREAKPT/WATCHPT/DBGRQ */ /* The core registers array and
ICE registers array. * the last array element must be NULL. */ core_register_t *regs; ice_register_t *ice_regs; /* target test data registers */ testdata_reg_t bypass; /* BYPASS regiseter */ testdata_reg_t idcode; /* Device ID code register */ testdata_reg_t instruction; /* instruction register */ testdata_reg_t scanpath; /* scan path select register */ /* test data of all scan chains
*/ u32 sc_num; /* the number of scan chains of the
target */ u32 active_sc; /* the current
active scan chain */ scan_chain_t sc[MAX_SCANCHAIN_NUM]; void *private; }target_t; List 4-4 数据结构target_t的定义 |
各数据结构成员的含义如下:
l
type:目标机CPU Core的类型。其值可能是TARGET_TYPE_ARM7TDMI或TARGET_TYPE_ARM9TDMI。
l
mode:目标机CPU的模式。对于ARM core而言,其CPU可能处于正常的32位ARM模式下,也可能处于16位的THUMB模式下。
l
status:目标机CPU的状态。对于ARM7TDMI core或ARM9TDMI
core而言,CPU可能处于正常的系统运行状态、调试状态或monitor状态。
l
regs指针:指向目标机CPU的core寄存器数组。NOTE:最后一个数组元素必须是NULL。
l
ice_regs指针:指向目标机CPU的ICE寄存器数组。NOTE:最后一个数组元素必须是NULL。
l
bypass寄存器:表示目标机CPU的JTAG接口中的bypass测试寄存器。
l
idcode寄存器:表示目标机CPU的JTAG接口中的idcode测试寄存器。
l
instruction寄存器:表示目标机CPU的JTAG接口中的instruction测试寄存器。
l
scanpath寄存器:表示目标机CPU的JTAG接口中的扫描路径选择寄存器。
l
sc_num:表示目标机CPU的JTAG接口中的扫描链个数。对于ARM7TDMI
Core和ARM9TDMI Core来说,这个值应该是3。
l
active_sc:表示目标机CPU的JTAG接口中当前选择的扫描路径。
l
数组sc:目标机CPU的JTAG接口中的扫描链。
l
指针private:指向目标机CPU特定的数据,它可能是NULL。
数据结构target_operation描述了一个函数跳转指针表,也即它定义了每种CPU core实现都应该提供的操作函数。该结构定义在include/target.h头文件中:
struct
target_operation { /* ICE register read/write
operations */ int (*ice_read) (u32 reg_addr,
u32 *reg_val); int (*ice_write) (u32 reg_addr,
u32 reg_val); /* target cpu core operations */ int (*halt) (void); int (*restart) (void); /* core registers read/write
operations */ int (*get_core_state) (void); /*
read all core regs */ int (*register_read)
(core_register_t *reg); int (*register_write) (core_register_t
*reg, u32 value); /* target memory read/write
operations */ int (*mem_read8) (u8 *buf, u32 address, u32
length); int (*mem_write8) (u8 *buf, u32 address,
u32 length); int (*mem_read16) (u16 *buf, u32
address, u32 length); int (*mem_write16) (u16 *buf, u32
address, u32 length); int (*mem_read32) (u32 *buf, u32
address, u32 length); int (*mem_write32) (u32 *buf, u32
address, u32 length); }; List 4-5 数据结构target_operation的定义 |
基于数据结构target_t和target_operation,目标机CPU Core接口在src/target/target.c源文件中定义了两个全局指针target和t_op,分别指向当前选定的目标机CPU
core及其操作函数指针跳转表。这两个全局指针也就是目标机CPU core接口模块向外部提供的统一接口。它们的定义如下:
/* the
unified interface of different target implementation */ struct
target_struct *target = NULL; struct
target_operation *t_op = NULL; List 4-6 目标机CPU core模块的统一接口 |
与JTAGER的Flash接口模块实现相关的源文件都被放在目录src/flash下。其中源文件src/flash/flash.c实现了这个模块的接口。
数据结构flash_t被用来描述目标板上的flash芯片。它定义在include/flash头文件中:
typedef
struct flash_chip_struct { char *name; /* the flash chip
name string */ u32 start_addr; /* the physical
start address */ int chip_size; /* how many bytes
the whole chip has */ int sector_size; /* how many
bytes per sector */ int block_size; /* how many bytes
per block */ int bit_width; /* it can only be
8, 16 or 32 */ int cfi_info_size; /* the words
number of flash CFI information */ /* flash operations */ int (* detect) (void); int (* cfi_query) (void *buffer); int (* erase_sector) (u32 addr); int (* erase_block) (u32 addr); int (* erase_chip) (void); int (* read) (void *buffer, u32
addr, u32 length); int (* write) (void *buffer, u32
addr, u32 length); /* * Its alias strings, and the last must be NULL. * NOTE: the flexible array member must be at the end of
struct. */ char * aliases[]; }flash_t; List 5-1 数据结构target_operation的定义 |
各成员的含义如下:
l
字符串指针数组name[]:该flash芯片的名字。NOTE:最后一个数组元数必须是NULL。
l
start_addr:该flash芯片在目标板上的起始物理地址。
l
chip_size:该flash芯片的大小,单位是字节。
l
sector_size:该flash芯片中每个扇区包含多少字节。
l
block_size:该flash芯片中每个块包含多少一个字节。
l
width:该flash芯片的位宽,比如:16位或32位。
l
函数指针detect:指向该flash芯片的检测函数。通常,我们可以通过读取flash芯片的软件ID来检测该flash芯片是否存在。
l
函数指针cfi_query:指向flash芯片的CFI信息读取函数。
l
函数指针erase_sector:指向一个用来擦除指定flash扇区的函数。
l
函数指针erase_block:指向一个用来擦除指定flash块的函数。
l
函数指针erase_chip:指向一个用来擦除整个flash芯片的函数。
l
函数指针read:指向读指定flash内存范围的函数。参数addr指定待读取的flash内存范围的起始物理地址;参数length指定读取多少个字(word),至于字的宽度则由flash芯片的位宽来决定。假如flash芯片是16位的,则一个字的宽度就是16位,即两个字节。假如flash芯片是32位的,则一个字的宽度就是32位,即4个字节。读回的结果将被保存到buffer所指向的缓冲区。
l
函数指针write:指向写指定flash内存范围的函数。参数addr指定待写入的flash内存范围的起始物理地址;参数length指定将要写入多少个字(word),至于字的宽度也是由flash芯片的位宽来决定的。缓冲区buffer中存有待写入的源数据。
每个被实现的flash芯片都必须定义一个flash_t类型的变量来描述它自己。比如,SST39LF/VF160芯片的实现就定义了flash_t类型的变量sst39vf160来描述它自己:
flash_t
sst39vf160 = { .name = NULL, .aliases = {"SST39VF160", "SST39LF160", NULL}, .start_addr = 0x0L, /* start physical address */ .chip_size = SST39_SIZE, .sector_size = SST39_SECTOR_SIZE, .block_size = SST39_BLOCK_SIZE, .bit_width = 16, .cfi_info_size = SST39VF160_CFI_INFO_SIZE, .detect = sst39vf160_detect, .cfi_query = sst39vf160_cfi_query, .erase_sector = sst39vf160_erase_sector, .erase_block = sst39vf160_erase_block, .erase_chip = sst39vf160_erase_chip, .read = sst39vf160_read, .write = sst39vf160_write, }; List 5-2 sst39vf160变量的定义 |
基于上述数据结构,我们在源文件src/flash/flash.c中定义了指针数组flashes[]来登记所有被定义的flash_t结构类型变量,如下所示:
/* all
supported flash chips */ flash_t
* flashes[] = { &sst39vf160, &sst39vf040, &sst28sf040, &am29f040, NULL /* the last element must be
NULL */ }; List 5-3 指针数组flashes |
注意:上述指针数组的最后一个元素必须是NULL。此外,我们还定义了全局指针flash来指向当前选定的flash芯片。如下所示:
/*
current selected flash */ flash_t
* flash = NULL; List 5-4 当前指针flash |
此外,我们还在src/flash/flash.c源文件中实现了一个检索函数flash_index()来根据芯片名字来在指针数组flashes[]中检索相应的flash_t结构变量。正是这三者构成了JTAGER的flash模块的统一接口。
本节将讨论如何让JTAGER支持新的flash芯片。在JTAGER中,由于Flash模块具有良好的可扩展性,因此实现这一点是非常容易的。下面我们将一步一步地讨论这一点。
首先,用户应该了解您想要支持的新flash芯片的属性,比如:它在目标板上的起始物理地址、位宽、每个扇区的大小或每个块的大小等。为了叙述的方便,我们假定用户想要增加的新flash芯片的名字为“xxx”。
接下来,在src/flash目录下增加一个源文件xxx.c。所有与xxx芯片实现相关的源代码都将被放置在这个新增加的xxx.c源文件中。
第三步,在源文件xxx.c中定义一个flash_t结构类型的变量xxx,如下:
flash_t
xxx = { .name = “xxx”, .aliases = {"xxx", NULL}, .start_addr = xxx_START_ADDR, /* start physical address
*/ .chip_size = xxx_CHIP_SIZE, .sector_size = xxx_SECTOR_SIZE, .block_size = xxx_BLOCK_SIZE, .width = 16, /* 8, 16 or 32 */ .cfi_info_size = xxx_CFI_INFO_SIZE, .detect = xxx_detect, .cfi_query = xxx_cfi_query, .erase_sector = xxx_erase_sector, .erase_block = xxx_erase_block, .erase_chip = xxx_erase_chip, .read = xxx_read, .write = xxx_write, }; List 5-5 flash芯片xxx的flash_t结构变量 |
第4步:编码实现List
5-5中列出各个函数和宏。
第5步:将结构变量xxx的地址登记到指针数组flashes[]中。如下:
/* all supported
flash chips */ flash_t
* flashes[] = { &sst39vf160, &sst39vf040, &sst28sf040, &am29f040, &xxx, NULL /* the last element must be
NULL */ }; List 5-6 指针数组flashes |
第6步:在src/flash目录的Makefile.am文件中增加源文件xxx.c,如下:
libflash_a_SOURCES
= \ flash.c \ sst39vf160.c \ sst39vf040.c \ sst28sf040.c \ am29f040.c \ xxx.c List 5-7 将xxx.c增加到源文件列表中 |
第7步:按照下列步骤重新生成configure脚本和各个Makefile.in文件:
#aclocal #autoheader #automake #autoconf |
最后一步:重新进行编译和安装JTAGER。请参考文档《JTAGER
User Manual》。
None.