Updating the First Stage Bootloader in Petalinux v2017.4

Overview

I am developing a prototype system that uses a lot (9) ZC706 boards. I need each board to boot with a different Ethernet MAC and IP address. I wanted this to be configured using the four DIP switches on the board.

Approach

To do this you need to modify the first stage bootloader (FSBL) to read the dip switch values and then pass the result to U-Boot. There is no built-in mechanism to do this so you need to modify U-Boot as well.

The plan is to modify the fsbl_hooks.c file in the FSBL to read the DIP switch value. Then it will place a message in the on-chip RAM at address 0xfffffc00. This location is unused by U-Boot. We then get U-Boot to look at the message to set the Ethernet address. Simple? Not hardly.

Modify fsbl_hooks.c

I’m not going to go over the FPGA logic for this change. It’s really pretty simple. Hook up the dip switch inputs to a GPIO unit in the PL. See my tutorials if you don’t already know how to do this. Next we modify the FsblHookBeforeHandoff function. First we need to read the DIP switch value. My GPIO block is at address 0x41200000 and I wired the switches into bits 8-11. So we first set those bits to inputs by writing 0xf00 to address 0x41200004 and then we read the dip switch values from address 0x41200000. We need to shift the result right by 8 to align the switch input values.

Next we create a string for U-Boot to consume. This is in the form of a command setting the U-Boot ethaddr variable to the desired MAC address.

u32 FsblHookBeforeHandoff(void)
{
	u32 Status;
	u32 dip_sw;
	int i;
	char ethaddr[40];

	Status = XST_SUCCESS;

	/*
	 * User logic to be added here.
	 * Errors to be stored in the status variable and returned
	 */
	Xil_Out32(0x41200004,0xf00); /* Set DIP switch as inputs */
	dip_sw = Xil_In32(0x41200000)>>8; /* Read values */

	fsbl_printf(DEBUG_INFO,"In FsblHookBeforeHandoff function \r\n");
	sprintf(ethaddr,"ethaddr=%02x:%02x:%02x:%02x:%02x:%02x\n",0x00,0x0a,0x35,0x00,0x00,dip_sw);
	for (i=0; i<strlen(ethaddr)+1; i++)
	  Xil_Out8(0xfffffc00+i,ethaddr[i]);

	return (Status);
}

Modify U-Boot

Next we need to get U-Boot to pay attention to the message. To do this we need to add the following to the project-name/project-spec/meta-user/recipes-bsp/u-boot/files/platform-top.h file.

#undef CONFIG_PREBOOT
#define CONFIG_PREBOOT	"echo U-BOOT for ${hostname};setenv preboot; echo;env import -t 0xFFFFFC00"

The trick is the env import -t 0xFFFFFCOO command. This tells U-Boot to import the string that the FSBL conveniently placed at that address.

That’s not so hard…

Great. That’s all we need to do right? Child, you’ve got a thing or two to learn. So far things have been easy. But even though petalinux-create creates the file components/plnx_workspace/fsbl/fsbl/src/fsbl_hooks.c for you to modify, it actually ignores that file completely. Likewise you could make a copy of the ZC706 BSP file and change the copy in there and then use your modified BSP file. Hah, amateur. That file is also ignored. You can modify the file in the Petalinux install directory but then all your Petalinux builds will get this little change. Clearly not desirable

What we need here is a patch file. We need to place a patch file in the components/plnx_workspace/fsbl/fsbl/src directory and then make a bbappend file that Petalinux will use to patch the fsbl_hooks.c file after it obtains the original file from god knows where but before it builds the FSBL.

Making the FSBL patch file

In order to build the patch you first need to check out the Xilinx embedded software git repository.

git clone https://github.com/Xilinx/embeddedsw.git

Now you need to checkout the branch for the version of Petalinux you are interested in. Here I’m using 2017.4.

$ cd embeddedsw/
$ git checkout tags/xilinx-v2017.4

Next create a branch and check it out. It doesn’t matter what you call the branch. Here I call the branch fsbl_mods_2017.4

$ git branch fsbl_mods_2017.4
$ git checkout fsbl_mods_2017.4
Now edit the file and make the changes.
$ emacs lib/sw_apps/zynq_fsbl/src/fsbl_hooks.c

Now we are ready to make the patch file. First add the file to the branch, and then commit the change.

$ git add lib/sw_apps/zynq_fsbl/src/fsbl_hooks.c
$ git commit -m "Patch to set MAC address based on dip switches" -s

Now we can create the patch.

$ git format-patch -1

This will create a lovely, albeit verbosely named, patch file

0001-Patch-to-set-MAC-address-based-on-dip-switches.patch

. How cool is that?

Here are the contents of my patch file.

From 1bc864fc9d064fd57c0721e27ca04e348d594bd9 Mon Sep 17 00:00:00 2001
From: Pete Johnson <pete@beyond-circuits.com>
Date: Mon, 21 May 2018 17:02:50 -0700
Subject: [PATCH] Patch to set MAC address based on dip switches

Signed-off-by: Pete Johnson <pete@beyond-circuits.com>
---
 lib/sw_apps/zynq_fsbl/src/fsbl_hooks.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/lib/sw_apps/zynq_fsbl/src/fsbl_hooks.c b/lib/sw_apps/zynq_fsbl/src/fsbl_hooks.c
index 304a6db..d86522a 100644
--- a/lib/sw_apps/zynq_fsbl/src/fsbl_hooks.c
+++ b/lib/sw_apps/zynq_fsbl/src/fsbl_hooks.c
@@ -130,6 +130,9 @@ u32 FsblHookAfterBitstreamDload(void)
 u32 FsblHookBeforeHandoff(void)
 {
 	u32 Status;
+	u32 dip_sw;
+	int i;
+	char ethaddr[40];
 
 	Status = XST_SUCCESS;
 
@@ -137,7 +140,13 @@ u32 FsblHookBeforeHandoff(void)
 	 * User logic to be added here.
 	 * Errors to be stored in the status variable and returned
 	 */
+	Xil_Out32(0x41200004,0xf00); /* Set DIP switch as inputs */
+	dip_sw = Xil_In32(0x41200000)>>8; /* Read values */
+
 	fsbl_printf(DEBUG_INFO,"In FsblHookBeforeHandoff function \r\n");
+	sprintf(ethaddr,"ethaddr=%02x:%02x:%02x:%02x:%02x:%02x\n",0x00,0x0a,0x35,0x00,0x00,dip_sw);
+	for (i=0; i<strlen(ethaddr)+1; i++)
+	  Xil_Out8(0xfffffc00+i,ethaddr[i]);
 
 	return (Status);
 }
-- 
1.8.3.1

Adding the patch file to Petalinux

Now we have our magic patch file. Next we need to add it to our Petalinux project. First we copy the patch file to the Petalinux project and rename it to something a little more sane.

$ cp 0001-Patch-to-set-MAC-address-based-on-dip-switches.patch petalinux-project/project-spec/meta-user/recipes-bsp/fsbl/files/0001-fsbl.patch

Next we create a file called petalinux-project/project-spec/meta-user/recipes-bsp/fsbl/fsbl_%.bbappend with the following contents

# Patch for FSBL
# Note: do_configure_prepend task section is required only for 2017.1 release
# Refer https://github.com/Xilinx/meta-xilinx-tools/blob/rel-v2017.2/classes/xsctbase.bbclass#L29-L35
 
do_configure() {
    if [ -d "${S}/patches" ]; then
       rm -rf ${S}/patches
    fi
 
    if [ -d "${S}/.pc" ]; then
       rm -rf ${S}/.pc
    fi
}
 
SRC_URI_append = " \
        file://0001-fsbl.patch \
        "
 
FILESEXTRAPATHS_prepend := "${THISDIR}/files:"

EXTERNALXSCTSRC = ""
EXTERNALXSCTSRC_BUILD = ""

This bbappend script is run during the bitbake Petalinux build and it will patch the appropriate fsbl_hooks.c file during the build.

But wait there’s more…

Now of course we still need to modify the platform-top.h file for U-Boot. Hilariously I went down the same route for U-Boot only to find that there is no platform-top.h file in the git repository for U-Boot. However there is one in the project-spec directory created by petalinux-config. Hmmm… fool me once…

But left with no other choice I tried it – and what do you know? The build does pay attention to this file and U-Boot seems to be build with the addition that we made.

Are we done yet?

Not hardly. As of this writing when I do a Petalinux build the version that is built will not boot. But if I compile the FSBL in the tree that I checked out with my changes I can use that FSBL and boot successfully and the MAC address will be set according to the dip switches. If anyone knows what is going on here I would sure appreciate some advice.

Conclusion

When all is said and done I can build a Petalinux which does what I want. But I don’t understand why modifying platform-top.h in the Petalinux directory works but modifying the fsbl_hooks.c does not. And I’d like to know why the FSBL which gets built with my petalinux build does not work. When I do a find while bitbake is making the FSBL I see the appropriately patched fsbl_hooks.c down in the bowels of the bitbake build. And if there are any (and I mean any) differences in the patch file or the bbappend file then I get errors when bitbake runs. And where did the platform-top.h file come from anyway? That doesn’t seem to be iin the U-Boot tree at all.

Sometimes I think this may all be by design in order to keep embedded Linux consultants employed. Let me know if you have any insights.

GPIO with ZYNQ and PetaLinux

Accessing GPIO controllers is pretty straightforward with PetaLinux, but there are a few tricks you need to know.

Locating the GPIO controller

In the example FPGA I am using, there are two GPIO controllers in the programmable logic. These are at address 0x4120_0000 and 0x4121_0000. If you look in the pl.dtsi file in your PetaLinux project, in the directory subsystems/linux/configs/device-tree, you will see entries for the GPIO devices. There’s no need to modify the entire device tree.

If you make a PetaLinux build and boot it, you can look in the /sys/class/gpio directory.

root@pz-7015-2016-2:~# ls /sys/class/gpio/                       
export       gpiochip901  gpiochip902  gpiochip906  unexport

You can see that there is a gpiochip directory for each GPIO controller. The gpiochip901 and gpiochip902 directories correspond to the PL controllers that I added in my design. The gpiochip906 directory is for the GPIO controller in the PS.

How will you know which is which, though? Each directory contains a label file which tells you the device tree label for the controller. You can go ahead and look at the contents:

root@pz-7015-2016-2:~# cat /sys/class/gpio/gpiochip901/label 
/amba_pl/gpio@41210000
root@pz-7015-2016-2:~# cat /sys/class/gpio/gpiochip902/label 
/amba_pl/gpio@41200000
root@pz-7015-2016-2:~# cat /sys/class/gpio/gpiochip906/label 
zynq_gpio

Looking at it, you’ll see that gpiochip901 corresponds to my controller at 0x4120_0000 and gpiochip902 corresponds to the controller at 0x4121_0000. Gpiochip906 is different, and corresponds to the built-in controller on the ZYNQ. Why those numbers? In my FPGA, the first GPIO controller controls only a single GPIO bit, while the second controls four bits. We can tell how many bits each controller controls by looking in the ngpio file for the controller.

root@pz-7015-2016-2:~# cat /sys/class/gpio/gpiochip901/ngpio 
1
root@pz-7015-2016-2:~# cat /sys/class/gpio/gpiochip902/ngpio
4
root@pz-7015-2016-2:~# cat /sys/class/gpio/gpiochip906/ngpio
118

It looks to me like the numbering starts at 901. Since that controller has only a single GPIO bit, the next controller is 902. That one has four bits, so the ZYNQ PS controller goes at 906, which has 118 bits.

Enabling the GPIO bits

To access a GPIO bit, you need to enable the correct GPIO pin. You do that by writing to the export file in the /sys/class/gpio directory. Here is an example of enabling the LSB of my second controller:

root@pz-7015-2016-2:~# echo -n 902 > /sys/class/gpio/export 

Now if you look in the /sys/class/gpio directory, you will see a new directory created which allows you to control the individual GPIO pin.

root@pz-7015-2016-2:~# ls /sys/class/gpio
export       gpio902      gpiochip901  gpiochip902  gpiochip906  unexport

If you look in that directory you see a number of controls:

root@pz-7015-2016-2:~# ls /sys/class/gpio/gpio902
active_low  direction   power       subsystem   uevent      value

Accessing the GPIO bits

You can determine the GPIO direction by looking at the direction file. Since my GPIO pin is an output, it gives the value out.

root@pz-7015-2016-2:~# cat /sys/class/gpio/gpio902/direction
out

You can change the value to a 1 by writing to the value file.

root@pz-7015-2016-2:~# echo 1 > /sys/class/gpio/gpio902/value           

Conclusion

So there you have it. The “official” way to access GPIO on PetaLinux.

SPI with PetaLinux on ZYNQ

Recently, I spent a lot of time trying to get SPI working on a PicoZed ZYNQ board under Linux. It was absolutely shocking how complicated this ended up being. One issue, I think, is that the device tree options differ depending on which version of PetaLinux you’re using. In this post, I’m going to document here how to do it with PetaLinux 2016.2.

Modify the device tree

First, you need to modify the system-top.dts file located in your PetaLinux project’s subsystems/linux/configs/device-tree directory. You need to add an entry that extends the existing entry for the SPI device. In the example, I am using spi0 on the processor subsystem. You can see the base definition for the SPI interface in the zynq-7000.dtsi include file in the same directory.

It’s important to note that PetaLinux will create an entry for the SPI device when you configure Linux– however, you won’t get a device file unless you add the entry for your particular SPI device. The trick is to add the SPI device information to the file system-top.dts. The device tree specification syntax allows you to make changes to the automatic entry for the SPI device by labeling a a node, then overlaying additional information onto the labeled node in other parts of the device tree specification.

In our case, the processor built-in SPI devices are labeled spi0 and spi1. I wanted to use spi0, so I added an entry in the system-top.dts file to add to the spi0 definition. In the example below, I’ve added three devices.

&spi0 {
  is-decoded-cs = <0>;
  num-cs = <3>;
  status = "okay";
  spidev@0x00 {
    compatible = "spidev";
    spi-max-frequency = <1000000>;
    reg = <0>;
  };
  spidev@0x01 {
    compatible = "spidev";
    spi-max-frequency = <1000000>;
    reg = <1>;
  };
  spidev@0x02 {
    compatible = "spidev";
    spi-max-frequency = <1000000>;
    reg = <2>;
  };
};

Rebuild linux and reboot your PicoZed board and you can now see the device files.

root@pz-7015-2016-2:~# ls -l /dev/spi*
crw-rw----    1 root     root      153,   0 Jan  1 00:00 /dev/spidev32766.0
crw-rw----    1 root     root      153,   1 Jan  1 00:00 /dev/spidev32766.1
crw-rw----    1 root     root      153,   2 Jan  1 00:00 /dev/spidev32766.2

Testing the SPI interface

In order to test the SPI interface, I built an FPGA with the SPI ports marked for debug. This allows me to use the embedded logic analyzer to view the pin activity from Vivado. PetaLinux ships with a program to test the SPI interface called spidev_test. I compiled it with the following command:

arm-xilinx-linux-gnueabi-gcc -o spidev_test /tools/xilinx/petalinux-v2016.2-final/components/linux-kernel/xlnx-4.4/Documentation/spi/spidev_test.c

Then, I copied it to my board using ssh, configured the logic analyzer to capture SPI activity, and ran the following command:

root@pz-7015-2016-2:~# ./spidev_test -D /dev/spidev32766.0 --speed 10000000
spi mode: 0x0
bits per word: 8
max speed: 10000000 Hz (10000 KHz)
RX | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | ................................

I could see the SPI pins wiggle in the logic analyzer view.

Conclusion

Anyway, I hope that this you save some time getting SPI to work.

PetaLinux for the PicoZed

I recently ran into a SNAFU when trying to build a PetaLinux image for a PicoZed board on a rev 2 FMC carrier board. The instructions assume that you have built an FPGA image and exported it from Vivado. You can check out my tutorial on PetaLinux for more background information, just in case you’re unfamiliar with the process.

You’re going to need to make sure that you have the board support package for the PicoZed board and carrier board you are using, which can download here. I like to keep the BSP in my Xilinx tool area, but you can store it anywhere you like. Be sure to unzip the download file.

Create the project

First, I source the settings file for PetaLinux. Don’t worry about the tftp warnings.

% . /tools/xilinx/petalinux-v2015.4-final/settings.sh

Now create your PetaLinux project. I created it as a sibling of the Vivado project directory.

% petalinux-create --type project --name pz_linux --source /tools/xilinx/pz_7015_2015_4.bsp

Configure the project

Next, you configure the project with the FPGA export. This sets the device tree for the build:

% petalinux-config --get-hw-description my_fpga/my_fpga.sdk --project pz_linux

Then, configure your filesystem:

% petalinux-config -c rootfs --project pz_linux

PetaLinux build fails

If we run petalinux-build right now, we run in to some trouble:

% petalinux-build --project pz_linux
INFO: Checking component...
INFO: Generating make files and build linux
INFO: Generating make files for the subcomponents of linux
INFO: Building linux
[INFO ] pre-build linux/rootfs/fwupgrade
[INFO ] pre-build linux/rootfs/gpio-demo
[INFO ] pre-build linux/rootfs/httpd_content
[INFO ] pre-build linux/rootfs/iperf3
[INFO ] pre-build linux/rootfs/peekpoke
[INFO ] pre-build linux/rootfs/weaved
[INFO ] build system.dtb
[ERROR] ERROR (phandle_references): Reference to non-existent node or label "usb_phy0"
[ERROR] ERROR: Input tree has errors, aborting (use -f to force output)
[ERROR] make[1]: *** [system.dtb] Error 255
ERROR: Failed to build linux

Fixing the device tree

The problem is that the device tree was overwritten by the petalinux-config. To fix that, we need to add the following lines to the system-conf.dtsi file located in pz_linux/subsystems/linux/configs/device-tree. Place the text following the entry for memory:

usb_phy0:phy0 {
  compatible="ulpi-phy";
  #phy-cells = ;
  reg = ;
  view-port = ;
  drv-vbus;
};

With this change in place, we can go ahead and run petalinix-build again, and the build will complete. I’m not exactly sure why this procedure is required or how to patch the PetaLinux install to fix this. I will explore that later.

Packaging

After building, you will need to package the distribution.

% petalinux-package --boot --format BIN --project pz_linux --fsbl pz_linux/images/linux/zynq_fsbl.elf --fpga my_fpga/impl_1/my_fpga.bit --u-boot

Copy the BOOT.BIN and pz_linux/images/linux/image.ub files to your MicroSD care and boot your PicoZed board.

Let me know if you have any ideas why this problem exists or how you can fix it.