How to Design and Access a Memory-Mapped Device in Programmable Logic from Linaro Ubuntu Linux on Xilinx Zynq on the ZedBoard, Without Writing a Device Driver — Part Two

Introduction

Following part one, this is the second half of a two part tutorial series on how access a memory-mapped device implemented in Zynq’s programmable logic fabric.

Recap

So far we’ve built a new ZedBoard project from scratch. It has a pair-of-32-bit-counters peripheral in the programmable logic. Thanks to the XPS Base System Builder Wizard, its processing system is preconfigured with support for UART, GPIO, SD card, Quad SPI, USB, Ethernet, and 512 MB of DRAM. We’ve built an FSBL that sets this all up, loads the PL, and loads and launches u-boot; and we’re going to reuse the same good old u-boot.elf Linux boot loader from last time.

Notice the system we have just built from scratch does not include the nice ADI HDMI display controller. Certainly we could have added our counters to that system, but to focus on the essentials I thought a brand new, minimalist design would be better. Not to worry, we can still use desktop Ubuntu over the network from another computer.

Headless desktop Linaro Ubuntu Linux

Install the VNC and RDP servers. From serial port console or Terminal, $ sudo apt-get install xrdp . Then you will be able to boot and run headless. You may still need to use the serial port console to determine the DHCP-assigned IP address ($ ifconfig).

Fear not. On your TeraTerm serial port console, get the IP address ($ ifconfig), and connect and login to your ZedBoard via RDP or VNC. All should work as before, even though you are not using any of the programmable logic fabric.

Device access via /dev/mem

The first, simplest, most elemental, most hacky, most dangerous way to access our peripheral from Linux is by opening /dev/mem and using mmap() to map a view of the device’s physical address space into our process’s virtual address space. Sven Andersson’s UIO blog entry summarizes these considerations perfectly so I’ll just quote him extensively here:

“… Here are some of the characteristics:

Userspace interface to system address space

Accessed via mmap() system call

Must be root or have appropriate permissions

Quite a blunt tool – must be used carefully

Can bypass protections provided by the MMU Possible to corrupt kernel, device or other processes memory

Pro

Very simple – no kernel module or code

Good for quick prototyping / IP verification

peek/poke utilities

Portable (in a very basic sense)

Con

No interrupt handling possible

No protection against simultaneous access

Need to know physical address of IP Hard-code?”

Continuing with our zed_counters project, with headless Ubuntu set up, let’s reboot our Linux system, configured to use the zed_counters’ design BOOT.BIN image we built earlier.

The SD card BOOT partition now contains our new BOOT.BIN with the zed_counters design; a new devicetree.dtb blog which denies any devices in the PL fabric, and the uImage Linux kernel we built last time. Safe-eject it, insert it into your ZedBoard, and reboot. Start a remote desktop connection. Open a Terminal. Fetch this /dev/mem access-based test application from Andersson’s blog entry.

Make the test program SETUID root so it can open the file. $ sudo chown root gpio-dev-mem-test; sudo chmod u+s gpio-dev-mem-test Congratulations, you have now added a massive security and robustness hole to your Linux system.

Try again. Read (write) the up-counter at address 0x70000000:

$ ./gpio-dev-mem-test -g 0x70000000 -i => … input: 00000000

$ ./gpio-dev-mem-test -g 0x70000000 -i => … input: 00000001

$ ./gpio-dev-mem-test -g 0x70000000 -i => … input: 00000002

$ ./gpio-dev-mem-test -g 0x70000000 -o 7

$ ./gpio-dev-mem-test -g 0x70000000 -i => … input: 00000007

$ ./gpio-dev-mem-test -g 0x70000000 -i => … input: 00000008

It counts! Read the free-running counter at address 0x70000004:

$ ./gpio-dev-mem-test -g 0x70000004 -i => … input: 577c9e79

$ ./gpio-dev-mem-test -g 0x70000004 -i => … input: 62502c56

$ ./gpio-dev-mem-test -g 0x70000004 -i => … input: 6b764783

It counts at 100 MHz.

It’s great that we can kick the tires on our new device without any device drivers or kernel configuration, but as Mr. Andersson writes, direct /dev/mem access is “OK for prototyping – not recommended for production.” Next we’ll see how to employ user-mode I/O to access just the counters device — with better safety and security.

Device Access via UIO

In this final section, we will see how to configure, modify, and rebuild your Linux kernel so you can configure UIO devices in your device tree, and how to access them from your application program.

Your ADI or Xilinx Linux source tree already contains the source to two useful UIO drivers, uio_pdrv (“UIO platform driver”) and uio_pdrv_genirq (“UIO platform driver with generic interrupts”). Unfortunately the default ADI and Xilinx build configurations do not build or include these drivers in the kernel (nor as loadable driver modules).

Reviewing the driver change history in github, we see this line was added in 11/2012 for “microblaze: UIO setup compatible property” to “Setup compatible property which matches petalinux”.

So “out of the box”, after enabling UIO drivers in menuconfig, the only UIO driver you can use that will properly initialize with a device tree blob configuration is uio_pdrv_genirq, and to use that, you must describe your peripheral as a “generic-uio”.

Furthermore, the uio_pdrv_genirq requires the device tree blob to also define the device interrupt number and the interrupt parent device.

To make this work for our interrupt-less counters device, we can lie, pick a free interrupt number, and pretend our counters are wired up to the Zynq GIC interrupt controller, just like interrupt-issuing Zynq peripherals do. Interrupt numbers are biased by -32 for some reason. Here is what the ensuing DTS device tree specification looks like:

We declare this device is a “generic-uio”, which will match uio_pdrv_genirq;

Its registers are at physical address 0x70000000 and are 0x1000 (4 KB) in size.

It generates interrupt 57+32 = 89. These are issued to the GIC. Not really.

Where did the fake interrupt number 57+32=89 come from? I reviewed the interrupt assignments in various DTS files that may intersect this project, such as arch/arm/boot/dts/{zynq.dtsi, zynq-zed.dtsi, zynq-zed-adv7511.dts}, and picked one that wasn’t in use there.

Now we can (and I have) built a devicetree blob from this DTS file, booted Linux, and my device has probed, configured, initialized, and set up a /dev/uio0 (major device 251, minor device 0), and (as we’ll see below) I have accessed this from a user application.

In fact if we already had a peripheral with both memory-mapped I/O and interrupts, this existing driver uio_gen_pdrv would be ideal, we’d be done, and this seemingly endless tutorial would be over already.

But since example device counters doesn’t have interrupts, I am still not satisfied. What does it take to get the interrupt-less uio_pdrv working with device tree configuration?

We have to modify drivers/uio/uio_pdrv.c to add an open firmware device id compatible string for it to match a device specification in the device tree. We’ll use the unremarkable name “uio_pdrv”.

We also have to bring some code forward (and now, since I’m not a Linux kernel developer, this is getting dangerously close to cargo cult programming) into the uio_pdrv_probe() function to dynamically allocate a uio_info structure for the dynamically discovered uio_pdrv instance.

For symmetry, we’ll add the compatible name “uio_pdrv_genirq” to the uio_pdrv_genirq device; it may then be used interchangeably with the existing name “generic-uio”.

Counters is now a uio_pdrv device with physical addresses 0x70000000-0x70000fff.

No interrupt fakery required!

Put that in arch/arm/boot/dts/zynq-zed-counters.dts, rebuild the device tree blob, and copy it to the SD card. $ make zynq-zed-counters.dtb;
$ cp arch/arm/boot/zynq-zed-counters.dtb /media/BOOT/devicetree.dtb

Together with the BOOT.BIN for our zed-counters design, we’re all set.

Safe-eject the SD card, insert it into the ZedBoard, and boot Linux!

There is no helpful console message confirming our counters device is initialized. That’s OK, we can see for ourselves.

Now to test UIO access to our counters. Andersson’s GPIO UIO test application is worthy of your study, but is not ideal to exercise our two counters. Instead, below, I have modified it (quickly hacked it) into a trivial counters-specific test. First we check counters[0] increments on every read. Next we do a few timing tests with our free running counters[1].

5 thoughts on “How to Design and Access a Memory-Mapped Device in Programmable Logic from Linaro Ubuntu Linux on Xilinx Zynq on the ZedBoard, Without Writing a Device Driver — Part Two”

Regarding the last few sentances regarding permission setting. If you are using udev, you could write a udev rule to change the permission on your /dev/ interface upon boot. People do similar things for block devices, etc. Just a thought.

Nice tutorial, most of the things still work with the new Xilinx EDK 14.7 version. You should only checkout the master branch of the hardware instead of the edk14.4 version. In addition, for the linux kernel you should checkout the xcomm_zynq_new_pcore_regmap. Than you have Linux kernel 3.12 running on the zedboard with linaro12.11

However, in the current revision of the Linux kernel 3.12 the UIO_pdrv part is not available anymore only the UIO_pdrv_genirq seems to exist. This prevents me to perform the last to create a UIO driver for the counter hardware. Hopefully the blog of kj6aku (see above) can help. Other help is appreciated.