FPGA Soft IP to Linux Application example

13 Mar 2017 - 23:51 | Version 120 |

Introduction

This document is reference to steps involved in the process of compiling a custom FPGA Soft IP to creating an embedded Linux system.

It describes in detail the Altera Specific tools required to build the binaries and also provides links to general reference documentation to help understand the necessery steps involved in developing software for embedded systems.

What you will need:

  • A supported SoC development board. In this lab we will be using Arria10. The steps should work for other development boards with difference being a different reference GHRD(Golden Hardware Reference Design) needs to be used as a baseline to create a hardware design with a new Soft IP. Some development boards might also require an additional preloader which inturn loads the next stage, Uboot(eg: Cyclone V/ Arria V).
  • A blank SD card to use with your development board and SD card reader.
  • One USB cable to connect the console port of your development board to your development host and allow a terminal emulation program to interact with the serial console.
  • A terminal emulator to run on your development host, like Putty, minicom, etc.
  • Access to GitHub
  • Some knowledge of how to use Altera's Quartus/Qsys , SoC EDS and linux.
  • Install Altera Tools - Quartus and SoC EDS.

The following will be demonstrated:

  • Overview and How To compile the hardware project
  • Arria10 Boot Flow overview
  • Toolchain
  • Overview and How To generate BootLoader
  • How to create Device tree overlay
  • Linux Kernel Compilation
  • Overview of Root FileSystem
  • Linux kernel driver for Soft IP
  • Userspace Application
This section presents the necessary board settings in order to run the generated binaries on the Altera Arria 10SoC development board.

a10board.png

First, confirm the following:
* DDR4 memory card is installed on HPS Memory socket
* SD Boot Card is installed on Boot Flash socket

Then the board switches need to be configured as follows:
* SW1: OFF-OFF-ON-ON
* SW2: all OFF
* SW3: OF-ON-ON-ON-ON-OFF-OFF-OFF
* SW4: all OFF

Also, the board jumpers need to be configured as follows:
* Place jumpers J16, J17
* Place jumper on J32 between pins 9 and 10 (1.8V option)
* Place jumper on J42 between pins 9 and 10 (1.8V option)
* Leave all other jumpers unplaced

Overview and How To compile the hardware project

The hardware design uses A10 GHRD as a baseline while adding a Timer component.

To compile the hardware project:
  • Host PC using Linux (CentOS 6.5 or Ubuntu 12.04LTS)
  • Altera Quartus II v16.1
  • Hardware project archive
Procedure:
Project Archive Hires_Timer_IP.tar.gz
Quartus Project ~/Hires_Timer_IP/A10_SoC.qpf
Qsys File ~/Hires_Timer_IP/hps_system.qsys
1. Retrieve and the archive file containing the hardware design and save it in the home folder.

2. Extract the files from the archive
$ cd ~
$ tar xzf Hires_Timer_IP.tar.gz

3. Make all the files writable (optional)
$ chmod +w -R ~/Hires_Timer_IP/

4. Start Quartus tool by double clicking the icon on the desktop, or by running it from the command line:
$ ~/intelFPGA/16.1/quartus/bin/quartus --64bit

5. In Quartus, go to File -> Open Project ..., browse to the file ~/Hires_Timer_IP/A10_SoC.qpf and click Open. qpf1.png

6. Quartus will load the project.

7.Compile the project. start compile.png

Generated Files

This section presents the name and location of the files resulted from compiling the hardware design.
File Description
~/Hires_Timer_IP/output_files/A10_SoC.sof SRAM Object File - for programming FPGA SRAM Object File - for programming FPGA
~/Hires_Timer_IP/hps_system.sopcinfo SOPC Info File - Used by Device Tree Generator
~/Hires_Timer_IP/hps_isw_handoff Handoff folder - Used by bootloader Generator

Converting .sof to .rbf

The SOF (SRAM Object File) file can use used to program the FPGA from the Quartus Programmer tool.

However, for the purpose of programming the FPGA from software, the SOF file needs to be converted to a RBF (Raw Binary File) format.

1. Start an embedded command shell

<span style="font-size: 1em; color: #000000; font-family: 'Bitstream Vera Sans Mono', 'Andale Mono', Courier, monospace; white-space: pre-wrap;">$ ~/intelFPGA/16.1/embedded/embedded_command_shell.sh</span>

2. Go to the GHRD folder

$ cd ~/Hires_Timer_IP/

3. Convert the file

$ quartus_cpf -c --hps -o bitstream_compression=on output_files/ghrd_10as066n2.sof output_files/ghrd_10as066n2.rbf

This will create the following file in the ~/Hires_Timer_IP/output_files/ folder:
* ghrd_10as066n2.periph.rbf - IO ring configuration file
* ghrd_10as066n2.core.rbf - FPGA fabric configuration file

Boot Flow

Includes the following stages

1. BootROM
2. U-Boot
3. Linux

boot flow A10.png

The following table presents a short description of the different boot stages:
Stage Description
BootROM Brings the processor out of reset, performs minimal configuration and loads U-Boot into OCRAM
U-boot Configures IO, FPGA, Setup PLLs and clocking, brings up DDRAM, loads Linux kernel
Linux Contains the process and memory management, network stack, device drivers and runs the end application
Elaborating the above steps

The boot ROM code, located in HPS, brings the processor out of reset and puts the processor in a stable known state. The boot ROM code is only aware of the second-stage boot loader and not aware of any potential subsequent software stages. During this time, the boot ROM also seamlessly handles any error conditions that may occur.The boot loader is located external to the HPS, either in off-chip flash memory or within the FPGA. If the FPGA is used, the second stage boot loader can execute directly from the FPGA without the need to copy it to on-chip RAM. The uboot then locates and loads Linux.Linux Boots and mounts root file system.

Bootloader:The main task of S/W bootloader is to load the OS and pass over the execution to it after setting up necessary environment for its setup. For this, the bootloader must first initialize the DDRAM (this includes setting up the controller).While accessing the flash memory, it should also perform bad block management.It also programs the FPGA. In Uboot configuration file, You will will find Env variables to set the file names for the device tree blob and root file system image and commands to load the Root Filesystem.

Go through file : ~/Hires_Timer_IP/software/uboot_bsp/uboot-spcfpga/include/configs/socfpga_arria10.h

Linux:When the kernel is loaded, it immediately initializes and configures all kernel features (serial console, kernel services - memory allocation, scheduling, file cache...) , configures the various hardware attached to the system, I/O subsystems, and storage devices and executes initcalls.

Altera specifics

If the bootloader is located in external flash then boot source is determined by a combination of boot fuses and BSEL pins. The options for boot sources are FPGA, NAND, SD/MMC, QSPI. To understand more about the settings for BSEL and boot fuses please read the boot user guide .For all flash devices, there is an area of memory called boot area and contains up to four bootloader images. The flash devices operate in raw and MBR(partition) mode. In raw mode, the boot image is located at the start of the flash memory device, at offset 0x0. In MBR mode:
  • The boot image is read from a custom partition (0xA2)
  • The first image is located at the beginning of the partition, at offset 0x0
  • Start address = partition start address
Reference

Embedded Linux overview Good reference material to get an overall understanding of different stages in embedded Linux development.

Arria10 Boot Guide Info related to Altera A10 specific boot settings

Altera Linux Workshop Altera Linux overview, covers boot flow, tools , configuration options and hardware address map overview

NAND boot Steps to boot linux when the boot source is NAND

QSPI boot Steps to boot linux when the boot source is QSPI

SDCARD boot Steps to boot linux when the boot source is SD card

Toolchain

Compiler that runs on the development machine, but generates code for the target .In case the U-boot or Linux kernel git trees are directly used, the build toolchain has to be manually downloaded. This section contains the instructions on how to download and setup the toolchain for this case. Note that the build toolchain is provided with the Poky Yocto recipes. So when the Yocto Project git tree is used, the recipes will download the build chain automatically.
The steps necessary to download and setup the build toolchain are:
$ cd ~
$ wget https://releases.linaro.org/archive/14.04/components/toolchain/binaries/gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux.tar.bz2</span>
$ tar xjf gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux.tar.bz2
$ export CROSS_COMPILE=~/gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux/bin/arm-linux-gnueabihf-

Bootloader

As discussed earlier, Control is passed from the BootRom to the boot loader. Uboot is responsible for basic initialization, loading and executing the kernel. Altera customized uboot, contains drivers needed to initialize the boot source (SD card/NAND/QSPI), program the FPGA. Besides these basic functions, most bootloaders provide a shell with various commands implementing different operations. ▶ Load device tree,Root Filesystem, kernel, memory inspection, hardware diagnostics and testing, etc

The uboot shell commands can be found here

A10 Specifics: Uboot programs both the core and the periph image to configure the FPGA. The following table highlights a few of the most relevant folders, related to the Altera Arria10.

Folder Description
arch/arm/cpu/armv7/socfpga_arria10 Architecture specific files, like clock manager, interrupt controller etc
arch/arm/include/asm/arch-socfpga_arria10/ Include files for the architecture specific source code
board/altera/socfpga_arria10/ Board specific files, platform dependent initializations.

Additional files which define configuration parameters are found in below files –

File Description
doc/README.SOCFPGA SoC specific readme file
arch/arm/cpu/armv7/socfpga_arria10/s_init.c First c function to initialize the critical hardware
include/configs/socfpga_arria10.h Arria10 configuration file
include/configs/socfpga_common.h Common configuration file for Arria10

include/configs/socfpga_arria10.h – has all the uboot configuration settings, tells what the command prompt should be named, environment setup – boot delay and a whole list of boot environmental variables, to tell the system how to program the rbf to configure the FPGA and how to boot into Linux(bootcmd,sdmmcboot,qspiboot,nandboot) and how to load the ext3 Root Filesystem. For our Soft IP project, we have to generate uboot and FPGA programming files(rbf’s) based on the new hardware design. The required steps:
  1. Open embedded command shell.
  2. Navigate to the hardware project
    $ cd ~/Hires_Timer_IP/
  3. Start the uboot generator by running command – bsp-editor&
  4. In the uboot generator, go to File->New HPS BSP
  5. In the new BSP HPS window, browse the directory to point to the handoff file ie (hps_isw_handoff) uboot 1.png uboot 2.png
  6. In the Generator window, make sure – Boot from SD/MMC is selected. RBF_filename: ghrd_10as066n2.periph.rbf uboot 3.png
  7. Click generate and then click exit
  8. Go to the U-Boot folder and run make:
         $ cd ~/Hires_Timer_IP/software/uboot_bsp
         $ make 
This will create the following files in the folder ~/a10_soc_devkit_ghrd/software/uboot_bsp:
  • u-boot_w_dtb.bin – concatenation of the U-boot binary and the device tree binary
  • uboot_w_dtb-mkpimage.bin – bootable image, containing 4 of the above images, each wrapped with the mkpimage header.

Reference

Linux kernel

linux flow.png The slide from Free electrons describes the user space vs kernel space interaction. The user space is where the user applications run. The C library provides the necessary system call interface to transition from user space to kernel space. This is required as user space and kernel space work on different protected address spaces. At the top is the system call interface which implements generic calls such as write/read/open . Below this is the kernel code (driver) which is platform independent and below this is the platform specific code(BSP – board support package).

Linux kernel has a lot of options available to allow user decide what features they want build in. When it comes to selecting drivers, it can be either built as part of the kernel image or as a module which can be loaded after linux boots up. This guide details the steps to build the Linux kernel using either Git tree or Yocto Armstrong recipe.

Yocto: The Angstrom buildsystem uses various components from the Yocto Project, most importantly the Openembedded buildsystem, the bitbake task executor, and various application and BSP layers. A linux based complete development environment which include all the tools required to build BSP – Bitbake (build engine), metadata – recipes , configs. OpenEmbedded core being a fundamental base layer of the build system.

Key components in Yocto: Metadata – files parsed by bitbake Config(.config) – provides global definitions of variables, user, layer configs. Classes(.bbclass) – commonly used logic and inheritance that can be used in recipes Recipes(.bb): instructions to build packages, tell where to obtain source files, patches Layers: collection of recipes, collection of metadata , application and distro recipes To get a complete understanding of how to customize and build bsp using Yocto, Refer to https://www.yoctoproject.org/docs/1.6.1/kernel-dev/kernel-dev.html Reference : https://rocketboards.org/foswiki/view/Documentation/AYoctoUserManualDanny https://rocketboards.org/foswiki/view/Documentation/GSRDCompilingLinux

Kernel Driver

Driver Model: There are several kind of devices connected to CPU using different kinds of bus interfaces. Some are discoverable (Pci , USB) . So when devices are connected to them, they are enumerated by the kernel and are given a unique identification to communicate with the CPU. The drivers probe routine gets triggered to initialize the device. There are other non-discoverable devices that are attached to the system (devices under i2c, spi and also in our case hardware modules). The kernel needs to be told about these devices. This is done using a device tree. The device tree explicitly tells how the peripherals are connected to the processor. This data is known as platform data and the compatible property suggest which driver would handle the device.

In device tree – check device tree section for detailed explanation
 high_res_timer: timer_driver@0x100000000 {
            compatible = "altr,timer_driver";
            reg = <0x00000001 0x00000000 0x00000020>;               
            interrupt-parent = <&intc>;
            interrupts = <0 19 4>;
         }; //end high_res_timer@0x100000000
On the other side, in device driver code – Binding the driver to the device:  Done by using Id_table
/* Specify which device tree devices this driver supports */
static struct of_device_id timer_driver_dt_ids[] = {
    {
        .compatible = "altr,timer_driver"
    },
    { /* end of table */ }
};

/* Inform the kernel about the devices this driver supports */
MODULE_DEVICE_TABLE(of, timer_driver_dt_ids);

static struct platform_driver timer_platform = {
    .probe = timer_probe,
    .remove = timer_remove,
    .driver = {
        .name = "timer_driver",
        .owner = THIS_MODULE,
        .of_match_table = timer_driver_dt_ids
    }
};
When platform driver structure is declared, it stores a pointer to “of_device_id”. The compatible property in that id table should be the same as mentioned in the dts. Now, when driver with the name timer_driver will get registered with platform bus, the probe routine will be called.

In the probe routine, the platform_get_resource() provides the ‘reg’ property mentioned in the dtb ie base address and range. Using which driver can request the memory region from the kernel and map it by doing devm_ioremap_resource.

driver probe.png

Function platform_get_irq() provides the property which is described by “interrupts” in dtb file. Thus, device tree provides all the board or Soft IP related information to the driver.

Misc Driver When modules are required to register their own small drivers, we use misc drivers. In Linux, every device is identified by two numbers major and minor. Major number 10 is allocated for a misc driver. Modules can register individual minor numbers with the misc driver and take care of small devices (soft Ip), needing only a single point of entry. The driver module uses the provided ‘misc_register’ and ‘misc_deregister’ to create its entry point for a minor number. The miscdevice structure has to be also be populated with information –
  • minor number: Every misc device must feature a different minor number, because such a number is the only link between the file in /dev and the driver.
  • name: the name for this device. Users will find the name in the /proc/misc file.
  • fops: pointer to the file operations which must be used to act on the device

In this driver we will implement the following 4 operations for the device Open, Read, Write, Unlocked_ioctl. We need a structure file_operation and the structure is defined in linux/fs.h

driver fops.png

The column on the left is the name of the operation that we want to support and, the value on the right is the name of the function that will implement the operation

Soft IP: The Soft IP is designed such that, the clock works at 250Mhz(T=4ns). To obtain a 10sec timer we have to set the load value to 2500million ie (10*10^9ns)/4ns = 2500million. One shot timer ensures timer is triggered only once ie when it reaches the load value and then the timer will roll back to 0.

Probe routine: In this routine when the Soft Ip registers with the driver and memory is mapped, we also initialize the Soft IP. The initialization enables the one short interrupt which will fire an interrupt 10sec after the module is loaded as load value is set 2500million.

IRQ routine: ISR routine is enabled by devm_request_irq().The ISR routine clears the Interrupt status. After this no more interrupts are generated.

Ioctl call: Ioctl which stand for Input Output control is a system call used in linux to implement system calls which are not available in the kernel by default. To implement a new ioctl command we need to follow the following steps.

  • Define the ioctl code in kernel driver code and include the same in the application code as well. The definition is follows #define "ioctl name" __IOX("magic number","command number","argument type")

where IOX can be :
"IO"
If the command neither reads any data from the user nor writes any data to the userspace.
"IOW"
If the commands needs to write some to the kernel space.
"IOR"
If the command needs to read some thing from the kernel space.
"IOWR"
If the command does both read as well as write from the user

The Magic Number is a unique number or character that will differentiate our set of ioctl calls from the other ioctl calls. Command Number is the number that is assigned to the ioctl. It is this number that is used to differentiate the commands from one another . The last is the type of data that will be written in case of __IOW or read in case of __IOR or both read as well as write in case of __IOWR. In the case of _IO we need not pass anything.

  • Add the header file linux/ioctl.h to make use of the above mentioned calls. Let us call the ioctl that we will create as "IOCTL_SET_LOADVAL" #define MAGIC_NO 100 #define IOCTL_SET_LOADVAL _IOWR(MAGIC_NO,0,unsigned int) The above is defined in both kernel module code as well as application code.
  • The next step is to implement the ioctl call we defined in to the corresponding driver. Function prototype: long ioctl(struct file *filp,unsigned int cmd,unsigned long arg) Our driver timer_ioctl is called when the user issues an ioctl command from the application code and passes a load value. The ioctl routine does the necessary formatting to send the information to the write call.

Write call: Is where most of the work is done for our timer driver. We make use of wait_event_interruptible to make sure that write call doesn’t interfere with the ISR or doesn’t get processed while the interrupt is being serviced. If the wait ends successfully, we unset the interrupt flag and then use the load value passed by the user through the IOCTL call in the application code to initialize register to again trigger a one shot interrupt. In our ISR, we must add some code to set the interrupt flag and wake up the processes waiting on the wait queue.
interrupt_flag = 1;
wake_up_interruptible(&interrupt_wq);
This link better explains the sleep mechanism used in drivers.

Kernel Configuration

Linux kernel configuration is usually found in the kernel source in the file: linux/.config . It is not recommended to edit this file directly but to use one of these configuration options: make config - starts a character based questions and answer session make menuconfig - starts a terminal-oriented configuration tool (using ncurses) If you have just downloaded the kernel source code, there will be no .config file.It can be created from scratch, by basing it of the "default configuration," taken from a running kernel , or taken from a distribution kernel release.When linux kernel is built, It needs to be built against a configuration file that dictates what, who and when will be included in the final kernel image.

To compile and build the kernel and module

  1. Download kernel source and build Linux kernel as described in Compile Linux Guide
  2. Put the linux driver source code in file hires_timer.c and copy it into the kernel source directory – linux-xxx/drivers/misc/
  3. Add these lines to drivers/misc/Kconfig file, which handles configuration settings so that our module can be enabled or disabled.
          config ALTERA_HIRES_TMR
                  tristate "Altera HIRES TIMER driver"
                  depends on ARM
                  default m
                  help
                    This enables the High RES driver for Altera SOCFPGA platform
         
    Line 3 states that this option can only be enabled if CONFIG_ARM is enabled and Line 4 states that this option should be enabled as default when CONFIG_ARM is enabled.
  4. To inform kernel to compile hires_timer.c when ALTERA_HIRES_TMR configuration is enabled, add line ‘obj-$(CONFIG_ALTERA_HIRES_TMR) += hires_timer.o’ to driver/misc/Makefile
  5. export CROSS_COMPILE as described in Toolchain section
  6. export ARCH=arm
  7. make socfpga_defconfig
  8. Enable Hires_Timer as a Module in Kernel Configuration Run ‘make ARCH=arm menuconfig’ Opens a window which lets you select the driver you need. If the driver is created under drivers/misc you will need to navigate to drivers/misc

kconfig 1.png kconfig 2.png kconfig 3.png

  1. Run `make zImage`
  2. Run `make modules`
  3. Run `make dtbs` to generate the default device tree - socfpga_arria10_socdk_sdmmc.dtb

The resulting images can be found –
zImage arch/arm/boot/
hires_timer.ko drivers/misc/
socfpga_arria10_socdk_sdmmc.dtb arch/arm/boot/dts/

Root FileSystem

A Root filesystem is necessary for kernel to start successfully.The root filesystem refers to the file system mounted at the base of the file system hierarchy, designated simply as /. The kernel will get a root filesystem, either as a ramdisk, passed as a pointer from the bootloader, or by mounting the block device given on the kernel command line by the root= parameter.Once it has a root filesystem, the kernel will execute the first program, by default named init.Then, as far as the kernel is concerned, its job is complete. It is up to the init program to begin processing scripts, start other programs, and so on, by calling system functions in the C library, which translate into kernel system calls.

Initrd and Initramfs Initramfs and initrd both provide essentially the same feature: they provide an initial root filesystem. For embedded systems, that alone might be enough: as soon as you've loaded initramfs or initrd, you're golden. initrd was used for Linux kernels 2.4 and lower and is now replaced by initramfs. initrd is a ram based block device which means that it required a fixed block of memory even if unused and as a block device, it requires a file-system. initramfs is made up from a cpio archive of files that enables an initial root filesystem and init program to reside in kernel memory cache, rather than on a ramdisk, as with initrd filesystems. Please refer to initrd and initramfs to understand the difference better.

Altera Specifics:

Root filesystem for Arria10 is built using the Angstrom recipe.You could either use the already built rfs or manually build it-

Manually build rfs
Rfs prebuilt binary

Arria10 Root filesystem guide This guide tells how to take an existing angstrom root filesystem and use it to create an initramfs and build it into the kernel.It also has links that describe the yocto and angstrom roof filesystem build process.

Device Tree and how to create an Overlay

A device tree is a data structure that describes the System-on- Chip(SoC) hardware: its various hardware components, and their relationship with one another. In the past, such information was hardcoded into the kernel for each SoC, and device trees were invented to curb this practice by providing a standard way to pass hardware description to the kernel. Much like source code, device trees can exist as human readable source files (.dts, .dtsi), or as compiled blobs (.dtb). Before a device tree is passed to the kernel, its source must be compiled into a blob.

Linux uses DT data for three major purposes:

  1. platform identification,
  2. runtime configuration, and
  3. device population.

Each module in the device tree is defined by a node and all its properties are defined under the node. For information on syntax: visit the official Device Tree Usage
Example:
/dts-v1/ /plugin/;
/ {
   fragment@0 {
      target-path = "/soc/base_fpga_region";
      #address-cells = <1>;
      #size-cells = <1>;

      __overlay__ {
         #address-cells = <2>;
         #size-cells = <1>;
         ranges = <0x00000001 0x00000000 0xff200000 0x00000020>;
         external-fpga-config;         
         high_res_timer: timer_driver@0x100000000 {
            compatible = "altr,timer_driver";
            reg = <0x00000001 0x00000000 0x00000020>;               
            interrupt-parent = <&intc>;
            interrupts = <0 19 4>;
         }; //end high_res_timer@0x100000000
      };
   };
};

The device tree is made of two part's

  • The live device tree prior to overlay being added
  • The device tree overlay
The live Device Tree must contain an FPGA Region, an FPGA Manager, and any FPGA Bridges. The FPGA Region's "fpga-mgr" property specifies the manager by phandle to handle programming the FPGA. If FPGA Bridges need to be involved, they are specified in the FPGA Region by the "fpga-bridges" property.

The Device Tree Overlay will contain:
  • "target-path" or "target" The insertion point where the the contents of the overlay will go into the live tree. target-path is a full path, while target is a phandle.
  • "ranges" The address space mapping from processor to FPGA bus(ses).
  • child nodes corresponding to hardware that will be loaded in this region of the FPGA.
  • "firmware-name" Specifies the name of the FPGA image file on the firmware search path. The search path is described in the firmware class documentation.

In the example above, when an overlay is applied targeting base_fpga_region, fpga_mgr@ffd03000 is used to program the FPGA. During programming, the firmware specified in the overlay is loaded to the FPGA using the FPGA manager specified in the region. If FPGA programming succeeds, the overlay makes it into the live device tree. The high_res_timer child device is then populated. If FPGA programming fails, the overlay is rejected. Since in our case FPGA is already programmed by the uboot, we have to define the boolean property external-fpga-config. Then the FPGA Region can be used to add child nodes for the devices that are in the FPGA.

Our Soft IP(High Resolution Timer) is hanging off the Light Weight H2F bridge(LWH2F) at offset 0x0 and using the FPGA to HPS IRQ #0. The ‘ranges’ property describes the base address and address range for child node high_res_timer. The first entry in ‘reg’ under high_res_timer is an index/chip select, in our case since the device is hanging of LWH2F - the chip select is 1. That along with the second entry is the reg address 0x100000000 which corresponds to base address 0xff200000 (Default Lwh2F base address)+ 0x00000000(offset of timer node). The third entry tells a range of addresses dedicated to the child node which will be memory mapped by the linux driver to talk to the device.

Determining what to fill in interrupt property:

The first number (zero) is a flag indicating if the interrupt is an SPI (shared peripheral interrupt)or PPI interrupts. 0 for SPI interrupts, 1 for PPI interrupts

The second number is related to the interrupt number.Referring to section GIC Interrupt Map(on page 9-13), we see that F2S_FPGA_IRQ0 has GIC interrupt Number 51, section General purpose signals(on page 28-16) tells which HPS component can be configured to provide FPGA to HPS interrupts. By this we conclude that since the interrupt used is FPGA to IRQ #0 in our Soft IP, we will have to set the interrupt value to (GIC interrupt number -32) = 19

The third number is the type of interrupt. Three values are possible:

  1. — Leave it as it was (power-up default or what the bootloader set it to, if it did)
  2. — Rising edge
  3. — Level sensitive, active high

This device tree overlay can be inserted into the live device tree after kernel boot up by issuing command-

dtbt -a dtbo-filename -p path
In our case, dtbt -a timer.dtbo -p /home/root

Reference:

https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/fpga/fpga-region.txt https://rocketboards.org/foswiki/view/Documentation/DeviceTreeGenerator131 https://rocketboards.org/foswiki/view/Documentation/WS2LinuxKernelIntroductionForAlteraSoCDevicesLab4SoCFPGALinuxKernel https://rocketboards.org/foswiki/view/Projects/Arria10DemoPR

Test linux boot and linux driver

Step 1:

Now, that we have all the required binaries we can start to populate the SD card. We can do this is multiple ways, i have shown 2 ways-

SD card layout:

gsrd-sdcard.png

The following table summarizes the information that is stored on the SD card:

Location File Name Description
Partition 1 socfpga_arria10_socdk_sdmmc.dtb Linux Device Tree Blob file
ghrd_10as066n2.core.rbf Compressed FPGA configuration file
ghrd_10as066n2.periph.rbf Compressed FPGA IO configuration file
zImage Compressed Linux kernel image file
Partition 2 various Linux root filesystem
Partition 3 n/a U-Boot Binary and U-Boot Device Tree image

Method 1:
  • Follow https://rocketboards.org/foswiki/view/Documentation/A10SGMIIRDV160BootLinuxFromSDCardto populate SD card with default images and see it boot up in the serial console.
  • Since we have new zImage , ghrd_10as066n2.core.rbf , ghrd_10as066n2.periph.rbf , socfpga_arria10_socdk_sdmmc.dtb, uboot_w_dtb-mkpimage.bin. The root filesystem remains unchanged. To update these images in the SD card –
    File Update procedure
    zImage Mount /dev/sdx1 (FAT) on the host machine and update files accordingly: $ sudo mkdir sdcard $ sudo mount /dev/sdx1 sdcard/ $ sudo cp sdcard/ $ sudo umount sdcard
    ghrd_10as066n2.core.rbf
    ghrd_10as066n2.periph.rbf
    socfpga_arria10_socdk_sdmmc.dtb
    uboot_w_dtb-mkpimage.bin In Embedded command shell (windows), $ alt-boot-disk-util -B uboot_w_dtb-mkpimage.bin -a write -d
    root filesystem Mount /dev/sdx2 (ext3 FS) on the host machine and updatefiles accordingly

Method 2:
  • Use make_sdcard.py script as described in Creating and Updating SDcard
  • Use Prebuilt root filesystem tar.gz as described above and use newly created binaries for zImage , ghrd_10as066n2.core.rbf , ghrd_10as066n2.periph.rbf , socfpga_arria10_socdk_sdmmc.dtb, uboot_w_dtb-mkpimage.bin as Bootloader and Kernel sections of this guide.

Step 2:

  • Mount /dev/sdx2 (root filesystem partition) on host machine
        $ mkdir ~/mnt
        $ mount /dev/sdc2 /mnt
        
  • Copy “hires_timer.ko” executable into the “/root” directory in the root filesystem
        sudo cp hires_timer.ko <path-to-rootfilesystem-partition>/root
        sync
        

Step 3:

  • Insert the Sd card into the board and boot it up.
  • At the linux kernel prompt –
    • Since we use a device tree overlay for our Soft IP driver, we have to issue command described in the Device tree overlay section. dtbt -a timer.dtbo -p /home/root
    • Insert the kernel module by issuing command – insmod hires_timer.ko
    • As described earlier, insmod invokes probe routine and probe routine reads the device tree for details related to IRQ, register mapping. It also initializes registers and enable one shot timer to fire interrupt after 10sec.
    • Hence, in the snapshot above you see a message from ISR routine after 10sec. Check the seconds duration in the timer stamp above (07:36:46 - 07:36:36 = 10sec).

driver load.png

Userspace Application

The userspace application provides a wrapper to the driver where most of the work is done. All it does is opens the /dev node created for the device (when you register a misc device in driver code by calling ‘misc_register()’, a /dev node is created with the name you provided) and takes user input – ‘w’ to either pass a load value which will eventually trigger a one shot interrupt or take user input – ‘r’ to read the timer register value and print to the console.

Compile and build the application-
  • export CROSS_COMPILE as described in Toolchain section
  • export ARCH=arm
  • Makefile used for this application is

makefile.png

Refer link, to understand more about the Makefile.
  • Run ‘make’
  • Copy “hires_timer_test” executable into the “/root” directory in the root filesystem
          sudo cp hires_timer_test  <path-to-rootfilesystem-partition>/root
          sync
          

Test the application, After Linux boots up and hire_timer.ko is inserted, you can run command as shown below. linux application.png

-- AmruthaSampath - 06 Mar 2017

Give us your feedback

© 1999-2017 RocketBoards.org by the contributing authors. All material on this collaboration platform is the property of the contributing authors. Privacy.