JTAG is a useful tool that allows customers additional debugging options. Segger was kind enough to send us a J-Link Plus probe for us to test. This blog post will describe how to setup your environment and use the J-Link to debug during both U-Boot and Kernel development.Note that all of the instructions below would work for all other J-Link probes.
Environment setup
Basic software setup
This blog post will focus on Linux installation but Windows setup should be very similar.First, you need to install the J-Link tools from Segger website:
From there you need to download the J-Link Software and Documentation Pack for your OS.The version v6.10m was used for this blog post, which is the latest version at the time of this writing.If you use an Ubuntu/Debian distro, the installation is very simple:
$ sudo dpkg -i JLink_Linux_V610m_x86_64.deb
Once installed, several tools and documentation will be installed, here are the important ones:
- UM08001_JLink.pdf: User Manual (located under
/opt/SEGGER/JLink_V610m/Doc/
) JLinkExe
: J-Link Commander tool that can be used for- verifying proper installation of the USB driver
- verifying the connection to the target CPU
- updating the firmware (automatically)
JLinkGDBServer
: GDB remote server- for GDB to connect to and communicate with the target
The latter tool is the most important since it will be used for every GDB debugging session. In our case, we want to use debug on ARM targets, so we need a cross ARM toolchain and GDB.For Ubuntu/Debian, you can simply install the following packages:
$ sudo apt-get install gcc-arm-linux-gnueabihf gdb-multiarch
Another solution is to use a toolchain from Linaro which includes the cross GDB binary:
The tools above are sufficient to start debugging using GDB manually.
Graphical IDE setup
If you wish to use a graphical IDE instead of entering GDB commands manually, there are some open-source solutions.For this blog post, we chose the most commonly used IDE: Eclipse. Although it was primarly made for Java developement, it now exists in different flavors.The version we are interested for U-Boot/Kernel development is the C/C++ one:
Once the tarball downloaded, you can extract to the folder of your choice.
$ tar xzf ~/Downloads/eclipse-cpp-neon-1a-linux-gtk-x86_64.tar.gz
$ ./eclipse/eclipse
You then need to install the GNU ARM Eclipse plug-in which contains J-Link debugging features. The plugin website explains the installation procedure in details:
Make sure to select J-Link Debugging when installing the plugin. ARM Plugin InstallThe J-Link part of the plugin is also well explained here.
Hardware setup
As you might have noticed, our boards have a tiny JTAG connector which is (much) smaller than the standard 20-pin connector.In order to connect the J-Link probe you threrefore need the following adaptation board:
This board comes with several jumpers. Make sure to place them on J3, J4 and J6 so the JTAG can properly communicate with the CPU.Also, make sure that the pin 1 (white dot) of the adaptation board matches the pin 1 of the board connector.Here is what it should look like for the BD-SL-iMX6. BDSL + J-LinkAt this point, if the board is powered, the JLinkExe
tool should detect a CPU is connected.
$ JLinkExe -device MCIMX6Q6 -if JTAG -speed 1000 -JTAGConf -1,-1
SEGGER J-Link Commander V6.10m (Compiled Nov 10 2016 18:38:45)
DLL version V6.10m, compiled Nov 10 2016 18:38:36
Connecting to J-Link via USB...O.K.
Firmware: J-Link V10 compiled Sep 1 2016 18:29:58
Hardware version: V10.10
S/N: 600102841
License(s): RDI, FlashBP, FlashDL, JFlash, GDB
VTref = 3.251V
Type "connect" to establish a target connection, '?' for help
J-Link>connect
Device "MCIMX6Q6" selected.
...
Debug architecture ARMv7.0
Data endian: little
Main ID register: 0x412FC09A
I-Cache L1: 32 KB, 256 Sets, 32 Bytes/Line, 4-Way
D-Cache L1: 32 KB, 256 Sets, 32 Bytes/Line, 4-Way
System control register:
Instruction endian: little
Level-1 instruction cache enabled
Level-1 data cache enabled
MMU enabled
Branch prediction enabled
Found 3 JTAG devices, Total IRLen = 13:
#0 Id: 0x4BA00477, IRLen: 04, IRPrint: 0x1, CoreSight JTAG-DP (ARM)
#1 Id: 0x00000001
#2 Id: 0x2191C01D
Cortex-A9 identified.
U-Boot debugging
In this section, it is assumed your board doesn't boot to U-Boot automatically. That means that the procedure below will explain how to initialize the DDR using the JTAG.In order to make sure that the board won't boot on its own, you can configure the boot DIP switch like for USB recovery. You can look at our unbricking blog post in order to see how to position the switch.Before debugging, you need to build your U-Boot binary. It is assumed that you've already read our article on that subject.Here is an example for the Nitrogen6x/BD-SL-i.MX6 (SabreLite):
~$ git clone https://github.com/boundarydevices/u-boot-imx6
-b boundary-v2016.03
~$ cd u-boot-imx6
~/u-boot-imx6$ export ARCH=arm
~/u-boot-imx6$ export CROSS_COMPILE=arm-linux-gnueabihf-
~/u-boot-imx6$ make nitrogen6q_defconfig
~/u-boot-imx6$ make all
Note that the output ELF binary ready to be debugged is named u-boot
.
Using GDB manually
First the GDB server needs to be started.
~$ JLinkGDBServer -device MCIMX6Q6 -if JTAG -speed 1000
The i.MX6SoloX requires a specific script for the server to be able to communicate with the CPU. It can be found here:
- https://wiki.segger.com/index.php?title=I.MX6_SoloX_Support
- script needs to be specified with the
-scriptfile
option
- script needs to be specified with the
Then a GDB session can attach to our local server (to the J-Link) in order to load the ELF binary.As said previously, the RAM needs to be initialized first before. In order to do so, a gdb init script must be provided to set the clocks and DDR registers like the DCD table would do.Such GDB init scripts have been created for all our boards:
For BD-SL-i.MX6 (Sabre-Lite), the nitrogen6x one must be used. The GDB command therefore looks like:
~/u-boot-imx6$ wget http://linode.boundarydevices.com/jlink/gdbinit_nitrogen6x
~/u-boot-imx6$ gdb-multiarch u-boot --nx -ix gdbinit_nitrogen6x
At this stage, the DDR and clocks are properly initialized. You now can load the ELF binary and start debugging. Here is an example.
(gdb) load
Loading section .text, size 0x51b34 lma 0x17800000
Loading section .rodata, size 0x130a4 lma 0x17851b38
...
(gdb) b print_cpuinfo
Breakpoint 1 at 0x17801bd4: file arch/arm/imx-common/cpu.c, line 184.
(gdb) c
Continuing.
Breakpoint 1, print_cpuinfo () at arch/arm/imx-common/cpu.c:184
184 {
(gdb) bt
#0 print_cpuinfo () at arch/arm/imx-common/cpu.c:184
#1 0x17848e64 in initcall_run_list (init_sequence=init_sequence@entry=0x17866950 )
at lib/initcall.c:31
#2 0x178140a4 in board_init_f (boot_flags=) at common/board_f.c:1059
#3 0x17803ac0 in _main () at arch/arm/lib/crt0.S:93
If you are not familiar with GDB, we highly recommend this article.
Using Eclipse IDE
Disclaimer: this section will only detail how to debug U-Boot from the IDE. If you wish to build U-Boot from eclipse, we recommend you to read this pdf.First, go to File > Import and select C/C++ Executable. Then enter the u-boot
binary path into the Select Executable field.Then the project is ready, the debug configuration now needs to be created. Go to Run > Debug Configurations and create a new GDB Segger J-Link Debugging profile.In the Debugger tab, adjust the settings to match the picture below (Device, Endianness, Connection etc..).Finally, in the Startup tab, copy the memU32
values from the GDB init script mentioned above to the box below.That's it, you can hit Debug and start debugging your U-Boot source code.
Linux kernel debugging
Unlike the U-Boot setup above, the assumption of this section is that the kernel (and its device tree) is loaded from U-Boot. This eases the setup quite a lot, the JTAG just needs to connect to the target and is ready to debug.For debugging purposes, we recommend using TFTP/NFS setup to load the kernel/device tree/OS.Before debugging, the kernel must be built. Here are the latest instructions to do so:
~$ git clone https://github.com/boundarydevices/linux-imx6.git
-b boundary-imx_4.1.15_2.0.0_ga
~$ cd linux-imx6
~/linux-imx6$ export ARCH=arm
~/linux-imx6$ export CROSS_COMPILE=arm-linux-gnueabihf-
~/linux-imx6$ make boundary_defconfig
~/linux-imx6$ make zImage dtbs modules
If you use TFTP, don't forget to copy the zImage
and dtb
file to the root directory of your TFTP server.The ELF binary that will be used for debugging is called vmlinux
Using GDB manually
Same as before, the GDB server needs to be started.
~$ JLinkGDBServer -device MCIMX6Q6 -if JTAG -speed 1000
Note that 1 JLinkGDBServer can only connect to 1 core, so if you want to debug several cores, you need to start as many GDB servers as cores. Each GDB server instance must use different ports than the other instances. Here is the command to connect to the Core1 for instance:
~$ wget http://linode.boundarydevices.com/jlink/MCIMX6-core-1.JLinkScript
~$ JLinkGDBServer -device MCIMX6Q6 -if JTAG -speed 1000
-scriptfile MCIMX6-core-1.JLinkScript
-port 2334 -swoport 2335 -telnetport 2336
Then a GDB session can attach to our local server (to the J-Link) in order to load the ELF binary.
~/linux-imx6$ gdb-multiarch vmlinux
At this stage, the DDR and clocks are properly initialized. You now can load the ELF binary and start debugging. Here is an example.
(gdb) target remote localhost:2331
(gdb) b init/main.c:start_kernel
Breakpoint 1 at 0x80c0099c: file init/main.c, line 493.
(gdb) b arch/arm/mm/proc-v7.S:cpu_v7_do_idle
Breakpoint 2 at 0x80119d60: file arch/arm/mm/proc-v7.S, line 72.
(gdb) c
Continuing.
Now that we have set our breakpoints, we can load the kernel from U-Boot. Here is an example using TFTP.
=> dhcp $fdt_addr $tftpserverip:$fdt_file
=> fdt addr $fdt_addr
=> run cmd_hdmi cmd_lcd cmd_lvds
=> tftp $loadaddr $tftpserverip:zImage
=> bootz $loadaddr - $fdt_addr
Then you should see the breakpoints on the GDB console.
Breakpoint 1, start_kernel () at init/main.c:493
493 {
(gdb) c
Continuing.
Breakpoint 2, cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:72
72 dsb @ WFI may enter a low-power mode
Using Eclipse IDE
Just like the U-Boot procedure, go to File > Import and select C/C++ Executable. Then enter the vmlinux
binary path into the Select Executable field.Then the project is ready, the debug configuration now needs to be created. Go to Run > Debug Configurations and create a new GDB Segger J-Link Debugging profile.As said before, if you want to debug multiple cores, you need to create several debug configurations. Below is the core0 setup.Note the difference with the U-Boot setup, here the Connect to running target is checked.Same here, note that the Initial Reset and Halt is unchecked.For other cores, the only differences are in the Debugger tab where you need to specify a scriptfile that you can find here. Also the ports must be different, see the Core1 setup below.Then you can create a Launch Group that would start the debugging session on all the cores at once.Finally you can start debugging all the cores.