How does the RPi boot?
An ARM booting is very different than
the Intel/AMD system (BIOS or UEFI). RPi's 1st stage bootloader is
on-chip, and looks for the file bootcode.bin in the boot partition:
pi@raspberrypi:~ $ mount
/dev/mmcblk0p1
on /boot type vfat
(rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
pi@raspberrypi:~ $ ls -gh /boot/bootcode.bin
-rwxr-xr-x 1 root 50K Jul 3 10:07 /boot/bootcode.bin
The
2nd stage bootloader runs on RPi's GPU (also on-chip), and requires the
start_<device>.elf executables, also in /boot partition:
-rwxr-xr-x 1 root 645K Jul 3 14:07 start_cd.elf
-rwxr-xr-x 1 root 4.8M Jul 3 14:07 start_db.elf
-rwxr-xr-x 1 root 2.8M Jul 3 14:07 start.elf
-rwxr-xr-x 1 root 3.8M Jul 3 14:07 start_x.elf
-rwxr-xr-x 1 root 18K May 15 19:09 bcm2708-rpi-3-b.dtb
-rwxr-xr-x 1 root 4.4M Jul 3 10:07 kernel7.img # for >= RPi2
-rwxr-xr-x 1 root 4.2M Jul 3 10:07 kernel.img # All other RPi
The
RPi boot loader running on the GPU reads config.txt (also in /boot) for
boot options, including which Linux kernel to load. There is no
"kernel" line in the config.txt right after a fresh install, so the boot
loader defaults to it loads the highest numbered
kernel<num>.img. Since we have a working system but want a boot
loader capable of booting multiple OS, we should point the RPi
bootloader to U-Boot image instead of the kernel<num>.img. [other
people seem to prefer renaming u-boot.bin to kernel8.img, taking
advantage of the boot loader's search for the highest numbered
kernel<num>.img.]
While booting the kernel, the RPi bootloader feeds the content of the file /boot/cmdline.txt to the kernel, but now I need U-Boot to do that. When you view this file, it can get overwhelming, but just note it down somewhere for now, so you can punch it into U-Boot down below.
Let's first back up this known working /boot partition to the development before messing with it.
parallels@ubuntu:$ git clone https://github.com/raspberrypi/tools
The tools are in the directory arm-bcm2708
$ ls arm-bcm2708/
arm-bcm2708hardfp-linux-gnueabi gcc-linaro-arm-linux-gnueabihf-raspbian
arm-bcm2708-linux-gnueabi gcc-linaro-arm-linux-gnueabihf-raspbian-x64
arm-rpi-4.9.3-linux-gnueabihf
I decided to use the Linaro toolchain, because it is popular among embedded developers. Note that the 32-bit version of the toolchain will not even run on a 64-bit host (that's pretty much all PCs these days). I need to put the toolchain's bin/ folder in $PATH, by adding the following line at the end of my ~/.profile, like this:
PATH="$HOME/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin:$HOME/bin:$HOME/.local/bin:$PATH"
[If using .profile, you have to logout and log back in for the change to take effect.]
In the toolchain's bin folder, there are a whole bunch of executables, like this:
~/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin$ ls
arm-linux-gnueabihf-addr2line arm-linux-gnueabihf-gfortran
...
To build a either the kernel or U-Boot, an environment variable called CROSS_COMPILE is necessary, as a prefix for the tool it should run. In the above case, that prefix is arm-linux-gnueabihf-, which I hard code in my .profile again.
$ git clone git://git.denx.de/u-boot.git
In the repo's configs/ folder, there are 4 RPi board configs:
~/rpi/u-boot$ ls configs/*rpi*
configs/rpi_2_defconfig configs/rpi_3_defconfig
configs/rpi_3_32b_defconfig configs/rpi_defconfig
These are the predefined U-Boot configs. Without understanding the details of the config, I will try using the 32b version for now. Remembering that I now have the environment variable CROSS_COMPILE, I seed this .config file from this defconfig:
~/rpi/u-boot$ make rpi_3_defconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/zconf.tab.o
HOSTLD scripts/kconfig/conf
#
# configuration written to .config
#
And then build the U-Boot binary itself with
~/rpi/u-boot$ make
If all goes well, you should see and the ELF binary (with symbols) and the stripped (without symbol) in the root folder, like this:
-rwxrwxr-x 1 parallels 2.2M Oct 1 12:00 u-boot
-rw-rw-r-- 1 parallels 372K Oct 1 12:00 u-boot.bin
Copy u-boot.bin to RPi's /boot folder.
To cause RPi boot loader to choose U-Boot instead of the kernel, just add a new line in the config.txt:
kernel=u-boot.bin
Overriding what the GPU boots this way (as supposed to changing the file that the GPU looks for) offers the possibility of backing out easily using the recovery method (which lets you modify the config.txt)--IF you are running the NOOBS install. Also enable UART in config.txt, for the debugging session to begin shortly below.
enable_uart=1
U-Boot 2017.09-00351-g7eefa35-dirty (Oct 11 2017 - 21:25:18 -0700)
DRAM: 896 MiB
RPI 3 Model B (0xa22082)
MMC: sdhci@7e300000: 0
...
Hit any key to stop autoboot: 0 Scanning mmc 0:1...
Note that the memory available to the CPU is 128 MB shy of 512 MB DRAM on board, because I gave 128 MB to the GPU (in /boot/config.txt).
Anyway it's clearly not booting to Linux, so I reboot and hit the Enter key to halt the U-Boot, and I am left with the U-Boot console:
U-Boot>
The next step is to configure U-Boot to load the kernel image already on /boot: kernel.img. Load the image and the DTB file, and give those to the bootz command in the mmc_boot command, as you can see below. [Note that "mmc 0:1" is pointing to the 2nd partition on the SD card--which is NOT the /boot partition for the NOOBS install (in which case it is the 0:6 partition).]
U-Boot> setenv fdtfile bcm2708-rpi-3-b.dtb
While booting the kernel, the RPi bootloader feeds the content of the file /boot/cmdline.txt to the kernel, but now I need U-Boot to do that. When you view this file, it can get overwhelming, but just note it down somewhere for now, so you can punch it into U-Boot down below.
Let's first back up this known working /boot partition to the development before messing with it.
Cross compiler toolchain for RPi
Remember throughout this article that the RPi is the target and my 64-bit Ubuntu 16.04 is the development host, which also implies that I am cross compiling the RPi's programs (starting with the bootloader) on the host, using a toolchain graciously provided by the Raspberry Pi folks on gihub. To clone the git repo:parallels@ubuntu:$ git clone https://github.com/raspberrypi/tools
The tools are in the directory arm-bcm2708
$ ls arm-bcm2708/
arm-bcm2708hardfp-linux-gnueabi gcc-linaro-arm-linux-gnueabihf-raspbian
arm-bcm2708-linux-gnueabi gcc-linaro-arm-linux-gnueabihf-raspbian-x64
arm-rpi-4.9.3-linux-gnueabihf
I decided to use the Linaro toolchain, because it is popular among embedded developers. Note that the 32-bit version of the toolchain will not even run on a 64-bit host (that's pretty much all PCs these days). I need to put the toolchain's bin/ folder in $PATH, by adding the following line at the end of my ~/.profile, like this:
PATH="$HOME/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin:$HOME/bin:$HOME/.local/bin:$PATH"
[If using .profile, you have to logout and log back in for the change to take effect.]
In the toolchain's bin folder, there are a whole bunch of executables, like this:
~/rpi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin$ ls
arm-linux-gnueabihf-addr2line arm-linux-gnueabihf-gfortran
...
To build a either the kernel or U-Boot, an environment variable called CROSS_COMPILE is necessary, as a prefix for the tool it should run. In the above case, that prefix is arm-linux-gnueabihf-, which I hard code in my .profile again.
Building U-Boot for RPi3
I clone the latest U-Boot repo:$ git clone git://git.denx.de/u-boot.git
In the repo's configs/ folder, there are 4 RPi board configs:
~/rpi/u-boot$ ls configs/*rpi*
configs/rpi_2_defconfig configs/rpi_3_defconfig
configs/rpi_3_32b_defconfig configs/rpi_defconfig
These are the predefined U-Boot configs. Without understanding the details of the config, I will try using the 32b version for now. Remembering that I now have the environment variable CROSS_COMPILE, I seed this .config file from this defconfig:
~/rpi/u-boot$ make rpi_3_defconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/zconf.tab.o
HOSTLD scripts/kconfig/conf
#
# configuration written to .config
#
And then build the U-Boot binary itself with
~/rpi/u-boot$ make
If all goes well, you should see and the ELF binary (with symbols) and the stripped (without symbol) in the root folder, like this:
-rwxrwxr-x 1 parallels 2.2M Oct 1 12:00 u-boot
-rw-rw-r-- 1 parallels 372K Oct 1 12:00 u-boot.bin
Copy u-boot.bin to RPi's /boot folder.
To cause RPi boot loader to choose U-Boot instead of the kernel, just add a new line in the config.txt:
kernel=u-boot.bin
Overriding what the GPU boots this way (as supposed to changing the file that the GPU looks for) offers the possibility of backing out easily using the recovery method (which lets you modify the config.txt)--IF you are running the NOOBS install. Also enable UART in config.txt, for the debugging session to begin shortly below.
enable_uart=1
U-Boot boots kernel7.img
When I reboot RPi with the HDMI monitor and keyboard connected, I see the U-Boot screen on the monitor with this message:U-Boot 2017.09-00351-g7eefa35-dirty (Oct 11 2017 - 21:25:18 -0700)
DRAM: 896 MiB
RPI 3 Model B (0xa22082)
MMC: sdhci@7e300000: 0
...
Hit any key to stop autoboot: 0 Scanning mmc 0:1...
Note that the memory available to the CPU is 128 MB shy of 512 MB DRAM on board, because I gave 128 MB to the GPU (in /boot/config.txt).
Anyway it's clearly not booting to Linux, so I reboot and hit the Enter key to halt the U-Boot, and I am left with the U-Boot console:
U-Boot>
The next step is to configure U-Boot to load the kernel image already on /boot: kernel.img. Load the image and the DTB file, and give those to the bootz command in the mmc_boot command, as you can see below. [Note that "mmc 0:1" is pointing to the 2nd partition on the SD card--which is NOT the /boot partition for the NOOBS install (in which case it is the 0:6 partition).]
U-Boot> setenv fdtfile bcm2708-rpi-3-b.dtb
U-Boot> setenv
bootcmd_mmc0 fatload mmc 0:1 ${kernel_addr_r} kernel7.img\;fatload mmc
0:1 ${fdt_addr} ${fdtfile}\;bootz ${kernel_addr_r} - ${fdt_addr}
This works because the default boot command--bootcmd--points to mmc_boot, as you can see in the boot command chain below:
U-Boot> printenv bootcmd
bootcmd=run distro_bootcmd
The distro_bootcmd, in turn, is a for loop, going through different boot hardware:
U-Boot> printenv distro_bootcmd
distro_bootcmd=for target in $boot_targets}; do run bootcmd_${target}; done
And the RPi3 config file set up multiple boot devices:
U-Boot> printenv boot_targets
boot_targets=mmc0 usb0 pxe dhcp
The kernel needs bootargs: the content of /boot/cmdline.txt that I saved away earlier. In U-Boot, you do that through an environment called bootargs. Because it is many lines long, it is easy to make a typo when creating this environment variable; so I do it in bite-sized chunks:
U-Boot> setenv rootargs "root=/dev/mmcblk0p2 rootfstype=ext4"
U-Boot> setenv fbargs "bcm2708_fb.fbwidth=1824 bcm2708_fb.fbheight=984 bcm2708_fb.fbswap=1"
U-Boot> setenv vcmemargs "vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000"
U-Boot> setenv miscargs "dwc_otg.lpm_enable=0 elevator=deadline fsck.repair=yes console=serial0,115200"
U-Boot> setenv bootargs ${rootargs} ${fbargs} ${miscargs}
${vcmemargs}
Note that I changed the rootfs device from a UUID to a more generic "2nd partition on the mmc device 0", to be more generic. I found that for a NOOBS install (vs. a straight Rasbian imaging as I did), the ext4 partition is the 6th partition is the 6th partition, so you can adjust it to mmcblk0p6 above.
I save the configuration change to the U-Boot environment file (persisted in /boot) before rebooting.
U-Boot> saveenv
U-Boot> boot
After a few seconds, I see that RPi3 is starting to boot Linux, and then voilà: I am back to the Rasbian desktop! I save the resulting /boot/uboot.env file for safe keeping. Since I want to start the FW before booting Linux, I studied U-Boot's standalone examples.
I can verify this by reading the ELF produced by the build.
parallels@ubuntu:~/rpi/u-boot/examples/standalone$ ${CROSS_COMPILE}readelf hello_world -h
...
Entry point address: 0xc100000
I can copy the build output (hello_world.bin) to the SD card's /boot partition, and run it in U-Boot shell (note that my /boot partition on the SD card is partition #1 on device #0):
U-Boot> fatload mmc 0:1 0xc100000 hello_world.bin
U-Boot>go 0xc100000 Hello world
## Starting application at 0x0C100000 ...
0xc100000
arg[1] = Hello
arg[2] = world
arg[3] = ""
## Application terminated, rc = 0x0
This shows that a stand-alone application can control low level peripheral.
~/rpi/u-boot/examples/standalone$ ../../tools/mkimage -A arm -O u-boot -T standalone -C none -a 0xc100000 -d hello_world.bin -v hello_world.img
Adding Image hello_world.bin
Image Name:
Created: Sat Oct 7 19:33:34 2017
Image Type: ARM U-Boot Standalone Program (uncompressed)
Data Size: 630 Bytes = 0.62 KiB = 0.00 MiB
Load Address: 0c100000
Entry Point: 0c100000
But this did not work, because U-Boot rejects it as an invalid kernel image.
pi@raspberrypi:~ $ gpio readall
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | IN | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | IN | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | JTAG_05 | IN | 1 | 7 || 8 | 1 | ALT5 | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | ALT5 | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | JTAG_07 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | JTAG_03 | IN | 0 | 15 || 16 | 0 | IN | JTAG_11 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | JTAG_13 | 5 | 24 |
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | JTAG_09 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | JTAG_05 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
To bounce a specified GPIO line, I can modify the hello_world program (I can create another stand-alone project, but I am feeling lazy) like this:
/* see BMC2835 peripheral datasheet p.92, downloadable from RPi website */
#define GPIO_ALT_FUNCTION_IN 0x0
#define GPIO_ALT_FUNCTION_OUT 0x1
#define GPIO_ALT_FUNCTION_0 0x4
#define GPIO_ALT_FUNCTION_1 0x5
#define GPIO_ALT_FUNCTION_2 0x6
#define GPIO_ALT_FUNCTION_3 0x7
#define GPIO_ALT_FUNCTION_4 0x3
#define GPIO_ALT_FUNCTION_5 0x2
/* from dwelch67's bare metal project */
#define BCM2708_PERI_BASE 0x3f000000
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000)
__attribute__((always_inline)) static inline void GPIOOutSet(int GPIO)
{
volatile uint32_t* GPIO_SET = (volatile uint32_t*)(GPIO_BASE + 0x1C);
GPIO_SET[GPIO >= 32] |= 1 << (GPIO & 0x1F);
}
__attribute__((always_inline)) static inline void GPIOOutClear(int GPIO)
{
volatile uint32_t* GPIO_CLR = (volatile uint32_t*)(GPIO_BASE + 0x28);
GPIO_CLR[GPIO >= 32] |= 1 << (GPIO & 0x1F);
}
/* Have to forward declare because whatever text that appears at the beginning
* is put at the entry point of the stand alone app.
*/
static void GPIOFunction(int GPIO, int functionCode);
int hello_world (int argc, char * const argv[])
{
app_startup(argv);
if (strcmp(argv[1], "gpio") == 0) {
if (argc < 3) {
printf("which pin? ");
return 0;
}
unsigned pin = 10 * (argv[2][0] - '0') /* simpler than sscanf */
+ (argv[2][1] - '0');
printf("%d\n", pin);
GPIOFunction(pin, GPIO_ALT_FUNCTION_OUT);
GPIOOutSet(pin); GPIOOutClear(pin);
}
return (0);
}
static unsigned _divide(unsigned n, unsigned d) {
unsigned q = 0;
while (n >= d) {
++q;
n -= d;
}
return q;
}
static void GPIOFunction(int GPIO, int functionCode)
{
int registerIndex = _divide(GPIO, 10);
int bit = (GPIO - 10 * registerIndex) * 3;
volatile uint32_t* GPIO_FSEL = (volatile uint32_t*)(GPIO_BASE + 0);
uint32_t oldValue = GPIO_FSEL[registerIndex];
uint32_t mask = 0b111 << bit;
GPIO_FSEL[registerIndex] = (oldValue & ~mask) /* don't touch others */
| ((functionCode << bit) & mask);
}
As you can see, it's a complete rewrite of the hello world stand alone application. But U-Boot's make system will dutifully produce a hello_world.bin with correct entry point.
parallels@ubuntu:~/rpi/u-boot$ ${CROSS_COMPILE}readelf examples/standalone/hello_world -h
...
Entry point address: 0xc100000
Start of program headers: 52 (bytes into file)
I then copied hello_world.bin to the SD card's /boot and tried it out in U-Boot command line:
U-Boot> fatload mmc 0:1 0xc100000 hello_world.bin
U-Boot> go 0xc100000 gpio 22
This is what saw in logic analyzer connected to the UART TX (J8.8) and J8.15:
UART TX is busy printing the printf statements, but the ~150 ns wide impulse is the GPIO bounce I wanted.
} else if (strcmp(argv[1], "jtag") == 0) {
printf ("Remapping GPIO to JTAG ...\n");
GPIOFunction(22, GPIO_ALT_FUNCTION_4);//TRST: JTAG 3
GPIOFunction(4, GPIO_ALT_FUNCTION_5);//TDI : JTAG 5
GPIOFunction(24, GPIO_ALT_FUNCTION_4);//TMS : JTAG 7
GPIOFunction(25, GPIO_ALT_FUNCTION_4);//TCK : JTAG 9
//Don't need: RPi doesn't "return" CLK
//GPIOFunction(26, GPIO_ALT_FUNCTION_4);//RTCK: JTAG 11
GPIOFunction(27, GPIO_ALT_FUNCTION_4);//TDO : JTAG 13
if (argc > 2 && strcmp(argv[2], "wait") == 0) {
printf ("Waiting ...\n");
while (!tstc());
(void) getc();
}
}
Now I need to find these pins on the J8 header. I marked them with a gray highlighter above. I use SEGGER J-Link, and the JTAG pin definitions are listed in the 20-pin J-Link connector section of the J-Link manual. When I run JLinkGDBServer, I see the this error:
parallels@ubuntu:~$ JLinkGDBServer -device cortex-a7
...
------Target related settings------
Target device: cortex-a7
Target interface: JTAG
Target interface speed: 1000kHz
Target endian: little
...
Target voltage: 3.31 V
Listening on TCP/IP port 2331
Connecting to target...ERROR: Cortex-A/R-JTAG (connect): Could not determine address of core debug registers. Incorrect CoreSight ROM table in device?
ERROR: Could not connect to target.
Target connection failed. GDBServer will be closed...Restoring target state and closing J-Link connection...
Shutting down...
Could not connect to target.
Please check power, connection and settings.
When I look at TDO (which is driven from from RPi), I see that the target is responding , as you can see here.
Connecting to target via JTAG
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
ARM AP[0]: 0x24770002, APB-AP
ROMTbl[0][0]: CompAddr: 80010000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][1]: CompAddr: 80011000 CID: B105900D, PID:04-004BB9D3
ROMTbl[0][2]: CompAddr: 80012000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][3]: CompAddr: 80013000 CID: B105900D, PID:04-004BB9D3
ROMTbl[0][4]: CompAddr: 80014000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][5]: CompAddr: 80015000 CID: B105900D, PID:04-004BB9D3
ROMTbl[0][6]: CompAddr: 80016000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][7]: CompAddr: 80017000 CID: B105900D, PID:04-004BB9D3
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
****** Error: Cortex-A/R-JTAG (connect): Could not determine address of core debug registers. Incorrect CoreSight ROM table in device?
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
Cannot connect to target.
I then tried OpenOCD instead.
$ openocd -f /usr/share/openocd/scripts/interface/jlink.cfg -f ~/rpi/openocd/rpi3.cfg
Open On-Chip Debugger 0.9.0 (2015-09-02-10:42)
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 : J-Link ARM V8 compiled Nov 28 2014 13:44:46
Info : J-Link caps 0xb9ff7bbf
Info : J-Link hw version 80000
Info : J-Link hw type J-Link
Info : J-Link max mem block 9224
Info : J-Link configuration
Info : USB-Address: 0x0
Info : Kickstart power on JTAG-pin 19: 0xffffffff
Info : Vref = 3.306 TCK = 1 TDI = 0 TDO = 1 TMS = 0 SRST = 1 TRST = 1
Info : J-Link JTAG Interface ready
Info : clock speed 1000 kHz
Info : JTAG tap: rspi.arm tap/device found: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)
Warn : JTAG tap: rspi.arm UNEXPECTED: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)
Error: JTAG tap: rspi.arm expected 1 of 1: 0x07b7617f (mfg: 0x0bf, part: 0x7b76, ver: 0x0)
Error: Trying to use configured scan chain anyway...
Warn : Bypassing JTAG setup events due to errors
Error: 'arm11 target' JTAG error SCREG OUT 0x00
Error: unexpected ARM11 ID code
It seems that JTAG pin mapping works, but the client side tools don't play well with RPi3. I will come back after getting a response back from SEGGER.
This works because the default boot command--bootcmd--points to mmc_boot, as you can see in the boot command chain below:
U-Boot> printenv bootcmd
bootcmd=run distro_bootcmd
The distro_bootcmd, in turn, is a for loop, going through different boot hardware:
U-Boot> printenv distro_bootcmd
distro_bootcmd=for target in $boot_targets}; do run bootcmd_${target}; done
And the RPi3 config file set up multiple boot devices:
U-Boot> printenv boot_targets
boot_targets=mmc0 usb0 pxe dhcp
The kernel needs bootargs: the content of /boot/cmdline.txt that I saved away earlier. In U-Boot, you do that through an environment called bootargs. Because it is many lines long, it is easy to make a typo when creating this environment variable; so I do it in bite-sized chunks:
U-Boot> setenv rootargs "root=/dev/mmcblk0p2 rootfstype=ext4"
U-Boot> setenv fbargs "bcm2708_fb.fbwidth=1824 bcm2708_fb.fbheight=984 bcm2708_fb.fbswap=1"
U-Boot> setenv vcmemargs "vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000"
U-Boot> setenv miscargs "dwc_otg.lpm_enable=0 elevator=deadline fsck.repair=yes console=serial0,115200"
U-Boot> setenv bootargs ${rootargs} ${fbargs} ${miscargs}
${vcmemargs}
Note that I changed the rootfs device from a UUID to a more generic "2nd partition on the mmc device 0", to be more generic. I found that for a NOOBS install (vs. a straight Rasbian imaging as I did), the ext4 partition is the 6th partition is the 6th partition, so you can adjust it to mmcblk0p6 above.
I save the configuration change to the U-Boot environment file (persisted in /boot) before rebooting.
U-Boot> saveenv
U-Boot> boot
After a few seconds, I see that RPi3 is starting to boot Linux, and then voilà: I am back to the Rasbian desktop! I save the resulting /boot/uboot.env file for safe keeping. Since I want to start the FW before booting Linux, I studied U-Boot's standalone examples.
U-Boot Hello World example
According to U-Boot documentation, cache coherence problem can be worked around by having U-Boot build system package up the application binary into a U-Boot image (RPi's kernel.img I've been working with above is one such example). U-Boot already builds a stand-alone hello_world example (examples/standalone), whose start address is defined in arch/arm/config.mk:
CONFIG_STANDALONE_LOAD_ADDR = 0xc100000
I can verify this by reading the ELF produced by the build.
parallels@ubuntu:~/rpi/u-boot/examples/standalone$ ${CROSS_COMPILE}readelf hello_world -h
...
Entry point address: 0xc100000
I can copy the build output (hello_world.bin) to the SD card's /boot partition, and run it in U-Boot shell (note that my /boot partition on the SD card is partition #1 on device #0):
U-Boot> fatload mmc 0:1 0xc100000 hello_world.bin
U-Boot>go 0xc100000 Hello world
## Starting application at 0x0C100000 ...
0xc100000
arg[1] = Hello
arg[2] = world
arg[3] = ""
## Application terminated, rc = 0x0
This shows that a stand-alone application can control low level peripheral.
Aside: cache problem
On the U-Boot main website's documentation page (as well as the README at the top of the U-Boot repo) there is some discussion about cache coherence problem because U-Boot runs all stand alone code with cache disabled. Supposedly, the workaround is to use the "bootm" script , which requires coaxing the U-Boot build system to produce a U-Boot image for this, I can copy the resulting file to the SD card.~/rpi/u-boot/examples/standalone$ ../../tools/mkimage -A arm -O u-boot -T standalone -C none -a 0xc100000 -d hello_world.bin -v hello_world.img
Adding Image hello_world.bin
Image Name:
Created: Sat Oct 7 19:33:34 2017
Image Type: ARM U-Boot Standalone Program (uncompressed)
Data Size: 630 Bytes = 0.62 KiB = 0.00 MiB
Load Address: 0c100000
Entry Point: 0c100000
But this did not work, because U-Boot rejects it as an invalid kernel image.
Modifying the hello_world example to toggle a GPIO line
Printing a string to console is nice, but controlling GPIO lines or SPI would be more practical for embedded applications. In an industrial application, an RPi system can signal that it is about to boot the Linux kernel by bouncing a GPIO line, or sending out a SPI/I2C packet. Let's say I want to toggle the RPi3 pin J8.15 right before booting the kernel. On the bcm2835~7 SoC, GPIO22 drives that J8.15 pin, as the command "gpio readall" (available in out-of-the-box Rasbian) shows (I yellow-highlighted it for you):pi@raspberrypi:~ $ gpio readall
+-----+-----+---------+------+---+---Pi 3---+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | IN | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | IN | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | JTAG_05 | IN | 1 | 7 || 8 | 1 | ALT5 | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | ALT5 | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | JTAG_07 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | JTAG_03 | IN | 0 | 15 || 16 | 0 | IN | JTAG_11 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | JTAG_13 | 5 | 24 |
| 10 | 12 | MOSI | IN | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | IN | 0 | 21 || 22 | 0 | IN | JTAG_09 | 6 | 25 |
| 11 | 14 | SCLK | IN | 0 | 23 || 24 | 1 | IN | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | IN | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | JTAG_05 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
To bounce a specified GPIO line, I can modify the hello_world program (I can create another stand-alone project, but I am feeling lazy) like this:
/* see BMC2835 peripheral datasheet p.92, downloadable from RPi website */
#define GPIO_ALT_FUNCTION_IN 0x0
#define GPIO_ALT_FUNCTION_OUT 0x1
#define GPIO_ALT_FUNCTION_0 0x4
#define GPIO_ALT_FUNCTION_1 0x5
#define GPIO_ALT_FUNCTION_2 0x6
#define GPIO_ALT_FUNCTION_3 0x7
#define GPIO_ALT_FUNCTION_4 0x3
#define GPIO_ALT_FUNCTION_5 0x2
/* from dwelch67's bare metal project */
#define BCM2708_PERI_BASE 0x3f000000
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000)
__attribute__((always_inline)) static inline void GPIOOutSet(int GPIO)
{
volatile uint32_t* GPIO_SET = (volatile uint32_t*)(GPIO_BASE + 0x1C);
GPIO_SET[GPIO >= 32] |= 1 << (GPIO & 0x1F);
}
__attribute__((always_inline)) static inline void GPIOOutClear(int GPIO)
{
volatile uint32_t* GPIO_CLR = (volatile uint32_t*)(GPIO_BASE + 0x28);
GPIO_CLR[GPIO >= 32] |= 1 << (GPIO & 0x1F);
}
/* Have to forward declare because whatever text that appears at the beginning
* is put at the entry point of the stand alone app.
*/
static void GPIOFunction(int GPIO, int functionCode);
int hello_world (int argc, char * const argv[])
{
app_startup(argv);
if (strcmp(argv[1], "gpio") == 0) {
if (argc < 3) {
printf("which pin? ");
return 0;
}
unsigned pin = 10 * (argv[2][0] - '0') /* simpler than sscanf */
+ (argv[2][1] - '0');
printf("%d\n", pin);
GPIOFunction(pin, GPIO_ALT_FUNCTION_OUT);
GPIOOutSet(pin); GPIOOutClear(pin);
}
return (0);
}
static unsigned _divide(unsigned n, unsigned d) {
unsigned q = 0;
while (n >= d) {
++q;
n -= d;
}
return q;
}
static void GPIOFunction(int GPIO, int functionCode)
{
int registerIndex = _divide(GPIO, 10);
int bit = (GPIO - 10 * registerIndex) * 3;
volatile uint32_t* GPIO_FSEL = (volatile uint32_t*)(GPIO_BASE + 0);
uint32_t oldValue = GPIO_FSEL[registerIndex];
uint32_t mask = 0b111 << bit;
GPIO_FSEL[registerIndex] = (oldValue & ~mask) /* don't touch others */
| ((functionCode << bit) & mask);
}
As you can see, it's a complete rewrite of the hello world stand alone application. But U-Boot's make system will dutifully produce a hello_world.bin with correct entry point.
parallels@ubuntu:~/rpi/u-boot$ ${CROSS_COMPILE}readelf examples/standalone/hello_world -h
...
Entry point address: 0xc100000
Start of program headers: 52 (bytes into file)
I then copied hello_world.bin to the SD card's /boot and tried it out in U-Boot command line:
U-Boot> fatload mmc 0:1 0xc100000 hello_world.bin
U-Boot> go 0xc100000 gpio 22
This is what saw in logic analyzer connected to the UART TX (J8.8) and J8.15:
UART TX is busy printing the printf statements, but the ~150 ns wide impulse is the GPIO bounce I wanted.
Enabling JTAG debugging on RPi3
Now that I can control the GPIO peripheral before booting the kernel, another cool thing to do is turn on the JTAG. On p. 102 of the CM2835 peripheral datasheet mentioned above, thre is a table of GPIO alternative functions. On the ALT4 column, GPIO22~26 are the ARM JTAG pins. I configure these pins for JTAG using a similar code as above.} else if (strcmp(argv[1], "jtag") == 0) {
printf ("Remapping GPIO to JTAG ...\n");
GPIOFunction(22, GPIO_ALT_FUNCTION_4);//TRST: JTAG 3
GPIOFunction(4, GPIO_ALT_FUNCTION_5);//TDI : JTAG 5
GPIOFunction(24, GPIO_ALT_FUNCTION_4);//TMS : JTAG 7
GPIOFunction(25, GPIO_ALT_FUNCTION_4);//TCK : JTAG 9
//Don't need: RPi doesn't "return" CLK
//GPIOFunction(26, GPIO_ALT_FUNCTION_4);//RTCK: JTAG 11
GPIOFunction(27, GPIO_ALT_FUNCTION_4);//TDO : JTAG 13
if (argc > 2 && strcmp(argv[2], "wait") == 0) {
printf ("Waiting ...\n");
while (!tstc());
(void) getc();
}
}
Now I need to find these pins on the J8 header. I marked them with a gray highlighter above. I use SEGGER J-Link, and the JTAG pin definitions are listed in the 20-pin J-Link connector section of the J-Link manual. When I run JLinkGDBServer, I see the this error:
parallels@ubuntu:~$ JLinkGDBServer -device cortex-a7
...
------Target related settings------
Target device: cortex-a7
Target interface: JTAG
Target interface speed: 1000kHz
Target endian: little
...
Target voltage: 3.31 V
Listening on TCP/IP port 2331
Connecting to target...ERROR: Cortex-A/R-JTAG (connect): Could not determine address of core debug registers. Incorrect CoreSight ROM table in device?
ERROR: Could not connect to target.
Target connection failed. GDBServer will be closed...Restoring target state and closing J-Link connection...
Shutting down...
Could not connect to target.
Please check power, connection and settings.
When I look at TDO (which is driven from from RPi), I see that the target is responding , as you can see here.
JLinkExe fails to find core debug registers
JLinkExe can even get some information out of the target as wellConnecting to target via JTAG
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
ARM AP[0]: 0x24770002, APB-AP
ROMTbl[0][0]: CompAddr: 80010000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][1]: CompAddr: 80011000 CID: B105900D, PID:04-004BB9D3
ROMTbl[0][2]: CompAddr: 80012000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][3]: CompAddr: 80013000 CID: B105900D, PID:04-004BB9D3
ROMTbl[0][4]: CompAddr: 80014000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][5]: CompAddr: 80015000 CID: B105900D, PID:04-004BB9D3
ROMTbl[0][6]: CompAddr: 80016000 CID: B105900D, PID:04-004BBD03
ROMTbl[0][7]: CompAddr: 80017000 CID: B105900D, PID:04-004BB9D3
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
****** Error: Cortex-A/R-JTAG (connect): Could not determine address of core debug registers. Incorrect CoreSight ROM table in device?
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
TotalIRLen = 4, IRPrint = 0x01
JTAG chain detection found 1 devices:
#0 Id: 0x4BA00477, IRLen: 04, CoreSight JTAG-DP
Cannot connect to target.
I then tried OpenOCD instead.
OpenOCD cannot connect either
OpenOCD does its own thing, but it can also get some response from RPi as well.$ openocd -f /usr/share/openocd/scripts/interface/jlink.cfg -f ~/rpi/openocd/rpi3.cfg
Open On-Chip Debugger 0.9.0 (2015-09-02-10:42)
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 : J-Link ARM V8 compiled Nov 28 2014 13:44:46
Info : J-Link caps 0xb9ff7bbf
Info : J-Link hw version 80000
Info : J-Link hw type J-Link
Info : J-Link max mem block 9224
Info : J-Link configuration
Info : USB-Address: 0x0
Info : Kickstart power on JTAG-pin 19: 0xffffffff
Info : Vref = 3.306 TCK = 1 TDI = 0 TDO = 1 TMS = 0 SRST = 1 TRST = 1
Info : J-Link JTAG Interface ready
Info : clock speed 1000 kHz
Info : JTAG tap: rspi.arm tap/device found: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)
Warn : JTAG tap: rspi.arm UNEXPECTED: 0x4ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x4)
Error: JTAG tap: rspi.arm expected 1 of 1: 0x07b7617f (mfg: 0x0bf, part: 0x7b76, ver: 0x0)
Error: Trying to use configured scan chain anyway...
Warn : Bypassing JTAG setup events due to errors
Error: 'arm11 target' JTAG error SCREG OUT 0x00
Error: unexpected ARM11 ID code
It seems that JTAG pin mapping works, but the client side tools don't play well with RPi3. I will come back after getting a response back from SEGGER.
device_tree_address=0x2effb600
kernel=u-boot.bin