Kampis Elektroecke

Lektion 1

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:

Signal
ALT4 ALT5
TDI GPIO26 GPIO4
TDO GPIO24 GPIO5
RTCK GPIO23 GPIO6
TMS GPIO27 GPIO12
TCK GPIO25 GPIO13
TRST GPIO22

Da nicht alle I/Os mit einer JTAG-Funktion über die Stiftleiste P1 zugänglich sind, ergeben sich die folgenden Kombinationen…

Signal I/O Funktion P1
TDI GPIO4 ALT5 7
TDO GPIO24 ALT4 18
RTCK GPIO23 ALT4 16
TMS GPIO27 ALT4 13
TCK GPIO25 ALT4 22
TRST GPIO22 ALT4 15

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.

J-Link Raspberry Pi JTAG Signal
1 1 VREF
3 15 nTRST
4 9 GND
5 7 TDI
7 13 TMS
9 22 TCK
11 16 RTCK
13 18 TDO

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 GitHub-Repository zum Download bereit.

→ Zurück zum Raspberry Pi ARM-Tutorial

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert