JTAGER User Manual

Version History:

- 0.2.0, 2004-09-25

- 0.1.0, 2003-11-30

Copyright (C) 2003 2004, Rongkai Zhan (Chinese Name: 詹荣开)


Table of Contents

1 Introduction

1.1 What is JTAGER

1.2 The List of Devices Supported

2 Build and Configuration

3 The Commands Set of JTAGER

3.1 Command Syntax Rules

3.2 Commands Group

3.3 Reset Command

3.4 Help Command

3.5 Idcode Command

3.6 Exit/quit Commands

3.7 Halt Command

3.8 Restart Command

3.9 Ice Command

3.10 Reg Command

3.11 Execbat Command

3.12 Memcmp Command

3.13 Memcpy Command
3.14 Memcsum Command

3.15 Memdump Command

3.16 Memset Command

3.17 Memtest Command

3.18 Flprobe Command

3.19 Flerase Command

3.20 Flwrite Command


Chapter 1 Introduction

1.1 What is JTAGER

JTAGER is a simple embedded system debugger running on the Linux host. By JTAGER, we can: 1) read or write the core registers of the CPU on the embedded target board. 2) Read or write the external device registers of the target board. 3) Read or write the RAM on the target board. 4) Read or write the solid storage device on the target board, such as Flash.


+------------+         +--------------------+         +--------------+
|            |         |                    |         |              |
|            |         |                    |         |              |
| Debug Host | <-----> | Protocol Converter | <-----> | Debug Target |
|            |         |                    |         |              |
|            |         |                    |         |              |
+------------+         +--------------------+         +--------------+
      |                           |
      |                           |
      V                           V
The host running         For example, Multi-ICE,
the debug toolkits       Wiggler-compatible JTAG
(such JTAGER)             dongle, etc.

Figure 1 A typical embedded debug system

 

To debug the embedded target board, we need to use a protocol converter based on JTAG interface to transfer signal and data between the host side and the target side. Figure 1 can demonstrate a typical embedded debug system.

 

Because it is very simple and very cheap, the MacriagorWiggler-compatible JTAG dongles are used most frequently. The circuit diagram can be found here:

 

Simple JTAG interface circuit.
==============================
 
PL1 25wayD Male                                                 PL2 20wayIDC
 
PL1/17-25 <--------------+-------------------------+--+-----------<  PL2/4,6,8,
                         |                         |  |              10,12,14,
                         |    AC244          200nF =  = 4.7uF        16,18,20
                         | +------------+  Vcc     |  |
         TDI          0v +-| 1       20 |-+--------+--+-----------<  PL2/1,2
PL1/5  >-------------------| 2       19 |-+
         TMS               | 3       18 |-----XXXX---------------->  PL2/5
PL1/3  >-------------------| 4       17 |     51R     
         TCLK              | 5       16 |-----XXXX---------------->  PL2/7
PL1/4  >-------------------| 6       15 |     51R
                           | 7       14 |-----XXXX---------------->  PL2/9
                      +----| 8       13 |     51R
                      |    | 9       12 |-----XXXX---+
                      |  +-| 10      11 |     51R    |
                      |  | +------------+            |
                      |  V 0v                        |
                      +------------------------------------------<   PL2/13
         TDO                                         |
PL1/11 <---------------------------------------------+
 
 
                            DTC114   /-------xxxx----------------<   PL2/15
         RST            10k       | /        51R
PL1/2  >----------------XXXX--+---|<
                              |   | \
                              X      V
                         47k  X      |  
                              X      |
                              |      |
                              V 0v   V 

 


1.2 The List of Devices Supported

(1) The types of target CPU core

 

(2) The types of JTAG interfaces

 

(3) The types of FLASH chips

 


Chapter 2 Build and Configuration

Because the root privilege is needed to install and run JTAGER, so we assume that you have get the root privilege of your Linux host. If not, please contact with the administrator.

 

First, download the JTAGER package and place it in some directory, such as the “/tmp” directory. Follow the following steps to unpack it.

 

#cd /tmp

#tar zxvf jtager-0.1.0.tar.gz

#cd jtager-0.1.0

 

Then, run configure script and compile:

 

#configure --prefix=/usr/local/jtager

#make all

 

Finally, run “make install” to install JTAGER to your specified path (the default path is /usr/local):

 

#make install

 

To run JTAGER, type “./jtager” and press <ENTER>:

 

#cd /usr/local/jtager/bin

#./jtager


Chapter 3 The Commands Set of JTAGER

JTAGER is a program driven by commands. When users run JTAGER program in shell, JTAGER will print its command prompt – “JTAGER:>”, after outputting some logo informations.

3.1 Command Syntax Rules

All JTAGER commands, command options and arguments are case-insensitive, and separated by the space character.

 

All JTAGER commands use the GNU-style long-named options. That is, each JTAGER command option is leaded by two consecutive chars ‘-‘. For example:

 

JTAGER:> ice --list

 

JTAGER command options may be followed its option argument, for example:

 

JTAGER:> memdump --base0x01c80000

 

JTAGER commands also maybe have non-option arguments. For example, in the following JTAGER command, “0x111” is a non-option argument:

 

JTAGER:> reg --name=r0 0x111

 

*) The rule for JTAGER command arguments representation

 

Any arguments are a string or an integer. For an integer argument, it can use the hexadecimal representation or the decimal representation. For example, these hex representations “0xff0 0Xff0 ff0H FF0h” can all be accepted by JTAGER. If not explicitly using hex representation like these, the arguments are treated as decimal number by default. All option arguments and non-option arguments of each JTAGER command comply with this rule.

 

*) The rule for memory block size representation

 

This rule is for the '--size' option, which is used to specify the memory block size. Anyhow, the argument of the '--size' option is sure to obey the rule for command arguments representation (see 1.1). To specify how many bytes (8-bit), how many half-words (16-bit), or how many words (32-bits), we can append a suffix character 'b' 'B' 's' 'S' 'w' or 'W' to the option argument of the '--size' option, and we call the suffix character "the size indicator suffix". For example:

 

        JTAGER:> memdump 0x01c80000 --size=0xfb

   or

        JTAGER:> memdump 0x01c80000 --size=0xfB

 

The above commands will dump the contents of the memory block [0x01C800000, 0x01C80000F], whose size is 0xF bytes.

 

ATTENTION PLEASE: Because the character 'b' or 'B' is also a hexadeciamal char, '--size=0xfb' doesn't means the memory block size is 251 (=0xFB) bytes. Therefore, if you want to specify the memory block size is 251 bytes, you should use '--size=0xFBB'.

 

To specify how many half-word the memory block is, we can use the following commands:

 

        JTAGER:> memdump 0x01c80000 --size=0xfs

    or

        JTAGER:> memdump 0x01c80000 --size=0xfS

 

NOTE: The suffix 's' or 'S' means short.

 

If there is no suffix character, we means how many words by default. For example, the following command will dump the memory block [0x01C00000, 0x01C0003C], whose size is 15 words:

 

        JTAGER:> memdump 0x01c80000 --size=0xf

 

Of course, you also can explicitly specify that its size is 15 words:

 

        JTAGER:> memdump 0x01c80000 --size=0xfw

   or

        JTAGER:> memdump 0x01c80000 --size=0xfW

 

NOTE: All '--size' options obey this rule. And the '--size' option has a default value: 1 word.


3.2 Commands Group

To more exactly express the function of each JTAGER command, we separate all JTAGER commands into two groups: 1) the base commands group. 2) The debug commands group.

 

The basic commands group contains five JTAGER commands:

 

The debug commands group contains the following commands:

 


3.3 Reset Command

The JTAGER reset command can be used to reset the JTAG link between your host side and your target side, and bring the TAP controller of your target CPU into the Run-Test/Idle state. You can refer to IEEE Standard 1149.1 – 1990 <<IEEE Standard Test Access Port and Boundary-Scan Architecture>> for more information about the TAP controller state machine.

 

This command has no any options or arguments. Command syntax:

 

reset
















3.4 Help Command

The JTAGER help command can show the help information of some specified JTAGER command, or print a list of all available JTAGER commands.

 

Command syntax:

 

help [[--cmdname=]cmdname]

 

The option argument cmdname is optional, and can be any legal JTAGER command name string. If the argument cmdname is not specified, the help command will print a list of all JTAGER commands. The list contains into two columns, which are separated by the tab character ‘\t’. The first column is command name string; the second one is the command description string.

 

For example, you can type “help help” to get the help information about the help command:

 

USAGE:

       help [[--cmdname=]cmdname]

 

DESCRIPTION:

       Show the list of all available JTAGER commands, or show the

verbose information of the specified JTAGER command.

 

PARAMETERS:

       --cmdname: The spcified JTAGER command name.


3.5 Idcode Command

The JTAGER idcode command is used to read the IDCODE value of your target CPU. Generally speaking, each embedded CPU with the support of JTAG test has a unique 32-bit IDCODE value. According to the JEDEC standard and <<IEEE Std 1149.1>>, the bit[0] of IDCODE value must always be 1, its bit[11 : 1] represents the unique manufacturer ID, its bit[27 : 12] represents the product ID number, and its bit[31 : 28] represents version number. Each kind of CPU has different IDCODE value. Refer to the BSDL file of your target CPU to get its specific IDCODE value.

 

The JTAGER idcode command has no any command options or arguments. Its command syntax:

 

idcode

 

Another use of the JTAGER idcode command is to check whether the link between host, JTAG emulator and target is ok. Before using the halt command to enter the debug state, user had better use the idcode command to read back the IDCODE value of your target. If the value we read back is equal to the specific IDCODE value of your target CPU, it seems that everything is ok J

 

For example, for author’s test board that is based on Samsung S3C44B0X CPU, the call to idcode command will print the following information:

 

Reading the JTAG Device ID code ... [OK]

Device ID = 0x1F0F0F0F

       bit[0] = 1, always be 1, required by IEEE Std 1149.1

       Manufacturer (bit[11:1]): 0x787 – SAMSUNG

       Part number (bit[27:12]): 0xF0F0

       Version (bit[31:28])   : 0x1

 

The value 0x1F0F0F0F read back is just the one the author expects for his Samsung S3C44B0X CPU.


3.6 Exit/quit Commands

Both the JTAGER exit command and the quit command can be used to exit JTAGER program. They are equivalent. Their command syntax:

 

exit or quit


3.7 Halt Command

The JTAGER halt command can make your target CPU enter into the debug state by sending DEBUG request to your target CPU. After receiving the DEBUG request, the target CPU will stop its current execution path and enter the debug state. When in the debug state, the target CPU is not driven by its internal clock pulse signal, and is fully controlled by the debug clock pulse signal from user’s host side.

 

The JTAGER halt command has no any options or arguments. Its command syntax:

 

halt

 

The JTAGER halt command is the bases of the other JTAGER debug commands (such as reg, mem and flash commands). Only if the target is halted into the debug state, the other debug commands can be used, otherwise they will print an error message like this:

 

Error: The target is running. Please use "halt" command to halt it.


3.8 Restart Command

The JTAGER restart command can end user’s current debug session created by the last call to the halt command, and make the target CPU return to the normal system state, that is, the target CPU resumes its execution from the last halted point.

 

Command syntax:

 

restart -–pc=address


3.9 Ice Command

The JTAGER ice command can read or write the contents of the EmbeddedICE-RT logic’s registers in your target CPU.

 

Command syntax:

 

ice [--list]

ice --name=regname [[--value=]newvalue]

 

When the command is called with no any options and arguments, it will read and print the contents of all ICE registers.

 

--list

The option is used to list all legal ICE registers short names. It is optional.

 

--name=regname

The option is used to specify the name of the register to read or write. Its option argument regnamemust be one of the names listed by the --list option.

 

--value=newvalue

The option --value represents the new value set for the specified ICE register. It is optional. If it is specified, then the specified new value will be written into the specified ICE register. Otherwise, the ice command will read the current value of the specified ICE register.


3.10 Reg Command

The JTAGER reg command can read or write the core registers of the target CPU. NOTE: this command must be used only if the target CPU is in the debug state.

 

The command syntax of this command is similar with the one of the JTAGER ice command. Its command syntax:

 

reg [--list]

reg --name=regname [[--value=]newvalue]

 

When the command is called with no any options and arguments, it will read and print the current contents of all core registers of your target CPU.

 

--list

The option is used to list all legal CPU core registers names. It is optional.

 

--name=regname

The option is used to specify the name of the register to read or write. Its option argument regname must be one of the CPU core register names listed by the --list option.

 

--value=newvalue

The option --value represents the new value set for the specified core register. It is optional. If it is specified, then the specified new value will be written into the specified CPU core register. Otherwise, the reg command will read the current value of the specified CPU core register.


3.11 Execbat Command

DESCRIPTION: Execute a jtager batch-command file
     SYNTAX: execbat /your/jtager/batch/file


A jtager batch command file is a file containing a sequence of jtager commands. The main purposal of a jtager batch command file is that we can write some jtager commands to initialize a raw target board. In a raw target board, the ram space is usually not available, and the UART is also not available too. Therefore, we can put some jtager commands used to setup the memory controller or UART port in a jtager batch command file.


In the source tarball of jtager, the "extra/s3c44b0x-init.bat" is a jtager batch command file for initializing my S3C44B0X develop board. When you write yourself jtager batch command file, you can use this file as a template.


For example, the following command will initialize my S3C44B0X board:


    JTAGER:> execbat /root/jtager/extra/s3c44b0x-init.bat
    *** line 12: reg --name=cpsr --value=0x000000D3
    CPSR register has been set new value 0x000000D3, successfully.
    *** line 17: memset 0x01D30000 --value=0x0
    *** line 22: memset 0x01d80000 --value=0x34021
    *** line 23: memset 0x01d80004 --value=0x7ff8
    *** line 24: memset 0x01d80008 --value=0x09
    ......

3.12 Memcmp Command

DESCRIPTION: Copy memory block from one to another
     SYNTAX: memcpy --to=addr1 --from=addr2 [[--size=]count]

The option '--to' specifies the base address of the destination memory block, while the option '--from' specifies the base address of the source memory block. The option '--size' specifies the size of these two memory blocks.

For example, the following command will copy the contents of the memory block [0x0C100000, 0x0C101000) to the memory block [0x0C200000, 0x0C201000):

    JTAGER:> memcpy --to=0x0C200000 --from=0x0C100000 --size=0x1000b
    memcpy: [0x0C100000, 0x0C101000) => [0x0C200000, 0x0C201000) ... [OK]
    Total 4096 bytes are copied.

After executing the above command, we run 'memcmp' command again, and will find these two memory blocks are the same:

    JTAGER:> memcmp --base1=0x0C100000 --base2=0x0C200000 --size=0x1000b
    memcmp: [0x0C100000, 0x0C101000) =?= [0x0C200000, 0x0C201000) .... [YES]

3.13 Memcpy Command

DESCRIPTION: Copy memory block from one to another
     SYNTAX: memcpy --to=addr1 --from=addr2 [[--size=]count]

The option '--to' specifies the base address of the destination memory block, while the option '--from' specifies the base address of the source memory block. The option '--size' specifies the size of these two memory blocks.

For example, the following command will copy the contents of the memory block [0x0C100000, 0x0C101000) to the memory block [0x0C200000, 0x0C201000):

    JTAGER:> memcpy --to=0x0C200000 --from=0x0C100000 --size=0x1000b
    memcpy: [0x0C100000, 0x0C101000) => [0x0C200000, 0x0C201000) ... [OK]
    Total 4096 bytes are copied.

After executing the above command, we run 'memcmp' command again, and will find these two memory blocks are the same:

    JTAGER:> memcmp --base1=0x0C100000 --base2=0x0C200000 --size=0x1000b
    memcmp: [0x0C100000, 0x0C101000) =?= [0x0C200000, 0x0C201000) .... [YES]

3.14 Memcsum Command

DESCRIPTION: Calculate the MD5 sum of a memory block
     SYNTAX: memcsum/md5sum [--base=]addr [--size=count]
      ALIAS: md5sum

The option '--base' specifies the base address of the memory block, while the option '--size' specifies the size of the memory block.

For example, the following command will calculate the MD5 sum of the memory block [0x0C100000, 0x0C101000):

    JTAGER:> md5sum 0x0C100000 --size=0x1000b
    e95065d361b843a7025a63830273fde8  memory block [0x0C100000, 0x0C100FFF]

From the above output information, we know that the MD5 sum of memory block [0x0C100000, 0x0C101000) is:
e95065d361b843a7025a63830273fde8 

3.15 Memdump Command

DESCRIPTION: Display the contents of a memory block
     SYNTAX: memdump [--base=]addr [--size=count]

For example, the following command will dump the memory block [0x0C100000, 0x0C100040) by the byte-output way:

    JTAGER:> memdump 0x0C100000 --size=64b
    0x0C100000: FF FF FB F7 FF FF FF FE   FF BF F7 FF FF FD FF FF   |................|
    0x0C100010: FF FF FF FF FF FD FF FF   FF F5 FB FF FF FF FE FF   |................|
    0x0C100020: FF FF FF FF FF FD FF FF   FF FF FF FF FF FF FF FD   |................|
    0x0C100030: FF FF FF FF EF FF FF FF   FF FF FF FF FF FF FF FE   |................|

The following command will dump the memory block by the memory block [0x0C100000, 0x0C100040) by the half-word output way:

    JTAGER:> memdump 0x0C100000 --size=32s
    0x0C100000: FFFF F7FB FFFF FEFF   BFFF FFF7 FDFF FFFF   |................|
    0x0C100010: FFFF FFFF FDFF FFFF   F5FF FFFB FFFF FFFE   |................|
    0x0C100020: FFFF FFFF FDFF FFFF   FFFF FFFF FFFF FDFF   |................|
    0x0C100030: FFFF FFFF FFEF FFFF   FFFF FFFF FFFF FEFF   |................|

And The following command will dump the memory block by the memory block [0x0C100000, 0x0C100040) by the word-output way:

    JTAGER:> memdump 0x0C100000 --size=16
    0x0C100000: F7FBFFFF FEFFFFFF   FFF7BFFF FFFFFDFF   |................|
    0x0C100010: FFFFFFFF FFFFFDFF   FFFBF5FF FFFEFFFF   |................|
    0x0C100020: FFFFFFFF FFFFFDFF   FFFFFFFF FDFFFFFF   |................|
    0x0C100030: FFFFFFFF FFFFFFEF   FFFFFFFF FEFFFFFF   |................|

3.16 Memset Command

DESCRIPTION: Modify the content of a memory block
     SYNTAX: memset [--base=]addr
             memset [--base=]addr --size=count --value=newval
             memset [--base=]addr --infile=pathname

The command 'memset' has two work modes: 'one-by-one' mode and 'fill' mode.

3.16.1 one-by-one mode


If you run 'memset' command with only one '--base' option, then we are in one-by-one work mode. For example:

    JTAGER:> memset 0x0C100000
    press 'q' or 'Q' or 'ENTER' to exit.
    0x0C100000: F7FBFFFF ? 0xAA55AA55
    0x0C100000: AA55AA55
    0x0C100004: FEFFFFFF ? 0x55AA55AA
    0x0C100004: 55AA55AA
    0x0C100008: FFF7BFFF ? 1234565
    0x0C100008: 0012D685
    0x0C10000C: FFFFFDFF ?

When in the 'one-by-one' mode, the memset command can let the user modify the memory unit in an interactive way, and it will echo the new value.

When in the 'one-by-one' mode and only in the 'one-by-one' mode, we also can append "the size indicator suffix" to the arguement of the '--base' option. For example:

    JTAGER:> memset 0x0C100000s
    press 'q' or 'Q' or 'ENTER' to exit.
    0x0C100000: AA55 ? 255
    0x0C100000: 00FF
    0x0C100002: AA55 ? 100
    0x0C100002: 0064
    0x0C100004: 55AA ?

 or

    JTAGER:> memset 0x0C100000b
    press 'q' or 'Q' or 'ENTER' to exit.
    0x0C100000: FF ? 0x11
    0x0C100000: 11
    0x0C100001: 00 ? 0x22
    0x0C100001: 22
    0x0C100002: 64 ?

3.16.2 fill mode


When in the 'fill' mode, the memset command will fill the specified memory block with the specified new value. And the default argument of the '--value' option is zero. For example:

    JTAGER:> memset 0x0C100000 --size=0x1000b
    memset: The memory block [0x0C100000, 0x0C101000) is filled with new value: 0x0
    Total 4096 bytes are filled.

The above command will fill the memory block [0x0C100000, 0x0C101000) with zero. If the '--size' option is missed, then the memset command will only fill one word. For example, the following command will set the word 0x0C100000 with the new value 0xAA55AA55.
   
    JTAGER:> memset 0x0C100000 --value=0xAA55AA55

When in the 'fill' mode, the memset command also can fill the specified memory block with the data from a host binary file. This can be done by the '--infile' option. For example, the following command will fill the memory block whose base address is 0x0C100000, with the binary data from the host file "/root/aa.bin":

    JTAGER:> memset 0x0c100000 --infile=/root/aa.bin
    memset: Fill the memory block [0x0C100000, 0x0C100022] with the file "/root/aa.bin" .... [OK]
    Total 35 bytes are written.

After executing the above command, we can use the memcsum command to verify whether the data within the memory block [0x0C100000, 0x0C100022] is the same as the data in the host binary file "/root/aa.bin":

    JTAGER:> memcsum 0x0c100000 --size=35b
    memcsum: [0x0C100000, 0x0C100022] ... [OK]
    MD5 sum: 11a5cbd1592478e831067e96b529cd9a

In your host shell, use the md5sum system command to calculate the MD5 sum of the binary file "/root/aa.bin":

    [root@zhanrk root]# md5sum aa.bin
    11a5cbd1592478e831067e96b529cd9a  aa.bin

We can see their MD5 sums are the same!

3.17 Memtest Command

DESCRIPTION: Test if a memory block is fully read-able and write-able.
     SYNTAX: memtest [--base=]addr [--size=count]

For example, the following command will test if the memory block [0x0C100000, 0x0C101000) is fully read-able
and write-able:

    JTAGER:> memtest 0x0C100000 --size=0x1000b
    memtest: testing if the memory block [0xC100000, 0xC100FFF] are fully read-able and write-able ... [OK]

Because the memory block is a ram block, it is fully read-able and write-able, of course. 

3.18 Flprobe Command

DESCRIPTION: Probe flash chips at the specified base address.
     SYNTAX: flprobe [[--chip-base=]addr]
    
The option '--chip-base' is optional, its default argument is 0x00. For example, the following command will
probe flash chip at the base address 0x00:

    JTAGER:> flprobe
    Probing flash chip at 0x00000000 ... [found : sst39vf160]
    base address: 0x00000000
       chip size: 2048 KB
     sector size: 4 KB
       bus width: 16-bit

3.19 Flerase command

DESCRIPTION: Erase one FLASH sector or the whole chip.
     SYNTAX: flerase --whole-chip
             flerase [--sector-address=]addr
    
For example, the following command will erase the flash sector [0x101000, 0x102000):

    JTAGER:> flerase 0x101000
    Erasing sst39vf160 flash sector [0x00101000, 0x00102000) ...... [OK]


3.20 Flwrite command

DESCRIPTION: Write data into Flash memory.
     SYNTAX: flwrite --to=addr1 --from=addr2 [[--size=]count]
             flwrite --to=addr1 --infile=pathname

For example, the following command will write the data of the memory block [0x0C100000, 0x0C104000) into
the flash memory block [0x101000, 0x105000):

    JTAGER:> flwrite --to=0x101000 --from=0x0c100000 --size=0x4000b
    Erasing sst39vf160 flash sector [0x00101000, 0x00102000) ... [OK]
    Erasing sst39vf160 flash sector [0x00102000, 0x00103000) ... [OK]
    Erasing sst39vf160 flash sector [0x00103000, 0x00104000) ... [OK]
    Erasing sst39vf160 flash sector [0x00104000, 0x00105000) ... [OK]
    Writing sst39vf160 Flash memory [0x00101000, 0x00105000) ....... [OK]

To verify whether the flash write operation is correct, we can use the 'memcsum' to calcute theiry MD5 sum:

    JTAGER:> memcsum 0x101000 --size=0x4000b
    5542f242c43a19f15a88400f044346d6  memory block [0x00101000, 0x00104FFF]
    
    JTAGER:> memcsum 0x0c100000 --size=0x4000b
    5542f242c43a19f15a88400f044346d6  memory block [0x0C100000, 0x0C103FFF]

You can see their MD5 sums are the same, so the flash write operation is correct.

Using the '--infile' option, you also can write the data from a host binary file into the specified flash memory block. For example, the following command will write the binary data from the host file "/root/aa.bin" into the specified flash memory block [0x00101000, 0x00101023):

    JTAGER:> flwrite --to=0x101000 --infile=/root/aa.bin
    Erasing sst39vf160 flash sector [0x00101000, 0x00102000) ... [OK]
    Writing sst39vf160 Flash memory [0x00101000, 0x00101023) ... [OK]
    Total 35 bytes are written.




Copyright (C) 2003-2004, Rongkai Zhan