Der Zugriff auf den Prozessor erfolgt mittels JTAG, eine Programmier- und Debugschnittstelle. Diese Schnittstelle ist per Default deaktiviert und muss somit vor der Benutzung eingeschaltet werden, indem die entsprechenden I/Os des Prozessors in den JTAG-Modus gesetzt werden. Sobald der JTAG aktiviert wurde, kann mit einem externen JTAG Debugger auf den Prozessor zugegriffen werden.
Hinweis:
Dieses Tutorial wurde mit einem Raspberry Pi B+ (Revision 0010) gemacht. Bei den Raspberry Pi Versionen mit 256 MB Arbeitsspeicher ist der GPIO27, welcher für den JTAG genutzt wird, nur über den CSI-Anschluss zu erreichen. Dieses Problem wurde mit der Revision 2 behoben. Ab Revision 2 sind alle JTAG-Pins über die 26-, bzw. 40-pol Stiftleiste P1 zu erreichen.
Für eine JTAG-Verbindung werden die folgenden Signale benötigt:
- Test Data Input (TDI) – Dateneingang des JTAG Schieberegisters
- Test Data Output (TDO) – Datenausgang des JTAG Schieberegisters
- Test Clock (TCK) – Taktsignal für die JTAG Logik
- Test Mode Select (TMS) – Steuersignal für den Zustandsautomaten des JTAG-Ports
- Test Reset (TRST) – Reset der Testlogik
Anders als bei Mikrocontrollern ist die JTAG-Schnittstelle des Prozessors nicht per Default aktiviert. Sie muss explizit aktiviert werden, da die JTAG-Funktionen alternative Funktionen einiger I/Os darstellen. Hier hilft das Datenblatt des BCM2835 weiter…
Für den JTAG können somit die folgenden Pins verwendet werden:
Da nicht alle I/Os mit einer JTAG-Funktion über die Stiftleiste P1 zugänglich sind, ergeben sich die folgenden Kombinationen…
Diese I/Os müssen nun in den JTAG-Modus versetzt werden. Dazu wird zuerst eine Datei namens main.s in einem Ordner namens source erstellt und mit dem nachfolgenden Code, der die benötigten I/Os in den JTAG-Modus versetzt, gefüllt.
ALT5 .req r1 ALT4 .req r2 .section .init .align 4 .globl _start _start: mov r3, #0x00 mov r4, #0x00 ldr r0, =0x20200000 mov r3, r1 lsl r3, #12 str r3, [r0, #0] mov r3, r2 lsl r3, #6 orr r4, r3 mov r3, r2 lsl r3, #9 orr r4, r3 mov r3, r2 lsl r3, #12 orr r4, r3 mov r3, r2 lsl r3, #15 orr r4, r3 mov r3, r2 lsl r3, #21 orr r4, r3 str r4, [r0, #8] loop$: b loop$
Dieser Code muss jetzt zu einem Kernelimage kompiliert werden. Dieses Image wird anschließend auf die SD-Karte des Raspberry Pi kopiert und dann bei jedem Einschalten des Raspberry Pi geladen. Die Erstellung des Kernelimages erfolgt mittels eines Makefiles und eines Linkerskriptes.
Ein Linkerskript hat die Aufgabe dem Linker mitzuteilen, in welche Speicherbereiche er welchen Code schreiben soll. In diesem Beispiel ist das Linkerskript recht einfach und soll lediglich die Sektion .init
, also die Sektion, die den Code enthält, an die Speicheradresse 0x8000 schreiben.
SECTIONS { .init 0x8000 : { *(.init) } .text : { *(.text) } .data : { *(.data) } /DISCARD/ : { *(*) } }
Der Wert der Speicheradresse ergibt sich aus dem Bootprotokoll des Linux-Kernels. Sobald der Bootloader den Kernel in den Speicher geladen hat, springt er diese Adresse an und startet die Ausführung des Kernels. Da das erstellte Programm den Kernel ersetzen wird, muss das Programm auch an die selbe Stelle im Speicher geschrieben werden.
Das Makefile kompiliert das Assemblerprogramm und erzeugt eine Objektdatei, welche dann vom Linker eingelesen und der Inhalt an die richtige Speicheradresse kopiert wird.
# Compiler ARMGNU ?= arm-none-eabi # Directories BUILD = build/ SOURCE = source/ # Input and output files TARGET = kernel.img MAP = kernel.map LINKER = kernel.ld OBJECTS := $(patsubst $(SOURCE)%.s,$(BUILD)%.o,$(wildcard $(SOURCE)*.s)) all: $(TARGET) $(LIST) rebuild: all $(TARGET) : $(BUILD)output.elf $(ARMGNU)-objcopy $(BUILD)output.elf -O binary $(TARGET) $(BUILD)output.elf : $(OBJECTS) $(LINKER) $(ARMGNU)-ld --no-undefined $(OBJECTS) -Map $(MAP) -o $(BUILD)output.elf -T $(LINKER) $(BUILD)%.o: $(SOURCE)%.s $(BUILD) $(ARMGNU)-as -I $(SOURCE) $< -o $@ $(BUILD): mkdir $@ clean: -rm -rf $(BUILD) -rm -f $(TARGET) -rm -f $(LIST) -rm -f $(MAP)
Anschließend wird die Datei gespeichert und über den Befehl make
kompiliert:
$ make arm-none-eabi-as -I source/ source/main.s -o build/main.o arm-none-eabi-ld --no-undefined build/main.o -Map kernel.map -o build/output.elf -T kernel.ld arm-none-eabi-objcopy build/output.elf -O binary kernel.img arm-none-eabi-objdump -d build/output.elf > kernel.list
Es wird wieder eine Datei mit dem Namen kernel.img erzeugt. Um das Programm auszuführen gibt es zwei Möglichkeiten:
- Es wird das kernel.img einer vorhandenen Raspberry Pi Installation ersetzt
- Auf einer SD-Karte wird eine FAT32 Partition mit einer Größe von etwa ~60 MB erzeugt. Anschließend werden die Dateien bootcode.bin und start.elf aus meinem Git-Repository, sowie das erzeugte Kernelimage auf die SD-Karte kopiert.
Sobald das Kernelimage auf die SD-Karte kopiert worden ist, kann der Raspberry Pi eingeschaltet werden. Der JTAG ist von nun an einsatzbereit.
Die JTAG-Pins werden nun mit einem entsprechenden ARM Debugger verbunden. Ich verwende einen Segger J-Link als Educational Version. Dieser Debugger kostet etwa 70€ und ist ein vollwertiger JTAG-Debugger (darf allerdings nur für nicht kommerzielle Zwecke genutzt werden!). Als Software kommt OpenOCD, GDB und ein Ubuntu Betriebssystem zum Einsatz. Bei OpenOCD handelt es sich um eine Open Source Debuggingsoftware, die mit verschiedenen Hardwaredebuggern arbeiten und einen Debugserver für die Verwendung von GDB bereitstellen kann.
Leider unterstützt die aktuelle Version von OpenOCD den J-Link nicht korrekt. Daher wird auf eine ältere Version zurückgegriffen:
$ wget https://github.com/gnu-mcu-eclipse/openocd/releases/download/v0.10.0-11-20190118/gnu-mcu-eclipse-openocd-0.10.0-11-20190118-1134-centos32.tgz $ tar -xvzf gnu-mcu-eclipse-openocd-0.10.0-11-20190118-1134-centos32.tgz
Jetzt müssen noch zwei Konfigurationsdateien angelegt werden. Eine ist für den verwendeten Debugger (hier der Segger J-Link) und die andere für das Target (hier der BCM2835 des Raspberry Pi).
# # SEGGER J-Link # # http://www.segger.com/jlink.html # interface jlink # The serial number can be used to select a specific device in case more than # one is connected to the host. # # Example: Select J-Link with serial number 123456789 # # jlink serial 123456789
adapter_khz 1000 adapter_nsrst_delay 400 reset_config none if { [info exists CHIPNAME] } { set _CHIPNAME $CHIPNAME } else { set _CHIPNAME rspi } if { [info exists CPU_TAPID ] } { set _CPU_TAPID $CPU_TAPID } else { set _CPU_TAPID 0x07b7617F } jtag newtap $_CHIPNAME arm -irlen 5 -expected-id $_CPU_TAPID set _TARGETNAME $_CHIPNAME.arm target create $_TARGETNAME arm11 -chain-position $_TARGETNAME rspi.arm configure -event gdb-attach { halt }
Zum Schluss muss der Raspberry Pi noch mit dem J-Link (bzw. dem verwendeten Debugger) verbunden werden.
Wenn ein anderer Debugger verwendet wird (z. B. ein FT232) müssen die Verbindungen und die entsprechende Konfigurationsdatei für den Debugger selbstverständlich angepasst werden.
Nun ist alles bereit und es kann versucht werden eine Verbindung zum Raspberry Pi herzustellen. Dazu wird der J-Link mit dem Computer verbunden und der Raspberry Pi (wenn noch nicht geschehen) mit Spannung versorgt. Anschließend kann OpenOCD gestartet werden. Als Parameter werden die Pfade zu den Konfigurationsdateien übergeben:
$ openocd/bin/openocd -f jlink.cfg -f raspberry.cfg
Es sollte nun die folgende Ausgabe erscheinen…
GNU MCU Eclipse 32-bit Open On-Chip Debugger 0.10.0+dev-00462-gdd1d90111 (2019-01-18-06:49) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html adapter speed: 1000 kHz adapter_nsrst_delay: 400 none separate Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'. Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : J-Link V10 compiled Feb 2 2018 18:12:40 Info : Hardware version: 10.10 Info : VTarget = 3.348 V Info : clock speed 1000 kHz Info : JTAG tap: rspi.arm tap/device found: 0x07b7617f (mfg: 0x0bf (Broadcom), part: 0x7b76, ver: 0x0) Info : found ARM1176 Info : rspi.arm: hardware has 6 breakpoints, 2 watchpoints Info : Listening on port 3333 for gdb connections
OpenOCD hat nun erfolgreich einen Debugserver gestartet, welcher über Telnet, bzw. GDB genutzt werden kann. Für einen Zugriff mittels GDB muss der entsprechende GNU-Debugger, hier arm-none-eabi-gdb, installiert werden.
$ sudo apt install gdb-arm-none-eabi
Hinweis:
Der verwendete Debugger muss für die Architektur des Targets kompiliert worden sein. Andernfalls wird eine Warnung ausgegeben und die Verbindung abgebrochen.
Remote debugging using localhost:3333 warning: while parsing target description (at line 4): Target description specified unknown architecture "arm" warning: Could not load XML target description; ignoring warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. Truncated register 16 in remote 'g' packet
Anschließend kann GDB gestartet und eine Verbindung mit OpenOCD hergestellt werden.
$ arm-none-eabi-gdb GNU gdb (7.10-1ubuntu3+9) 7.10 Copyright (C) 2015 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=x86_64-linux-gnu --target=arm-none-eabi". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". (gdb) target remote localhost:3333 Remote debugging using localhost:3333 0x00008000 in ?? () (gdb)
Nun wollen wir GDB nutzen um ein Programm per JTAG auf den Raspberry Pi zu laden und auszuführen. Das Programm soll die Act-LED, welche sich an GPIO 47 befindet, einschalten.
Hinweis:
Beim Raspberry Pi B+ befindet sich die Act-LED an GPIO 47 und ist Active-High beschaltet. Bei einem Raspberry Pi B befindet sich die LED an GPIO 16 und ist Active-Low beschaltet.
Der Programmcode für das gewünschte Programm sieht folgendermaßen aus:
.section .init .globl _start .section .text _start: ldr r0, =0x20200000 ldr r1, [r0, #0x10] mov r2, #0x07 lsl r2, #0x15 bic r1, r2 mov r2, #0x01 lsl r2, #0x15 orr r1, r2 str r1, [r0, #0x10] mov r1, #0x01 lsl r1, #0x0F str r1, [r0, #0x20] loop$: b loop$
Jetzt muss das Programm noch kompiliert werden. Dazu wird der Compiler arm-none-eabi genutzt. Im Gegensatz zum ersten Programm wird hier kein Kernelimage benötigt, da dieses Programm nicht von einem Bootloader geladen werden muss. Stattdessen wird ein einfaches Hex-File erzeugt.
$ arm-none-eabi-as LED.s -g -o LED
Hinweis:
Mit dem Argument -g
werden zusätzliche Debuginformationen generiert. Diese sind für ein eventuelles Debugging äußerst nützlich.
Über die geöffnete GDB Instanz wird das kompilierte Programm mit dem Befehl file
geladen und über den Befehl load
auf das Target kopiert.
(gdb) file LED Reading symbols from LED...done (gdb) load Loading section .text, size 0x38 lma 0x0 Start address 0x0, load size 56 Transfer rate: 448 bits in <1 sec, 56 bytes/write. (gdb)
Mit dem Befehl c
wird das Programm dann ausgeführt.
(gdb) c Continuing.
Und schon sehr ihr die Act-LED des Raspberry Pi aufleuchten. Somit funktioniert die komplette Debugchain und ihr könnt über GDB nun jedes Programm mittels JTAG debuggen.
Hinweis:
Als Alternative kann auch auf das Embedded Studio der Firma Segger zurückgegriffen werden. Es wird allerdings nur die verwendete ARM-CPU und nicht das komplette SoC des Kleinrechners unterstützt. Dafür wird das Debuggen und die Codeentwicklung vereinfacht.
Das komplette Projekt, inkl. Boot-Partition für die SD-Karte, OpenOCD und allen Programmen, steht in meinem GitLab-Repository zum Download bereit.
Schreibe einen Kommentar