Getting the resources

Having the kernel module driver loaded, it’s time to get control of the hardware’s resources. That is, being able to read and write to the registers, and receiving its interrupts.

Still on the same entry in the device tree,

    xillybus_0: xillybus@50000000 {
      compatible = "xlnx,xillybus-1.00.a";
      reg = < 0x50000000 0x1000 >;
      interrupts = < 0 59 1 >;
      interrupt-parent = <&gic>;

      xlnx,max-burst-len = <0x10>;
      xlnx,native-data-width = <0x20>;
      xlnx,slv-awidth = <0x20>;
      xlnx,slv-dwidth = <0x20>;
      xlnx,use-wstrb = <0x1>;
    } ;

we now focus on the part marked in bold.

The driver typically takes ownership of the hardware’s memory segment in the probing function (which is the one that is pointed to in the “probe” entry of the platform_driver structure, declared for the driver, e.g. xilly_drv_probe() for Xillybus’ driver).

We’ll look at the skeleton of a typical probing function  (please don’t copy from this code, but rather from a real, working driver):

static int __devinit xilly_drv_probe(struct platform_device *op)
{
  const struct of_device_id *match;

  match = of_match_device(xillybus_of_match, &op->dev);

  if (!match)
    return -EINVAL;

The first operation is a sanity check, verifying that the probe was called on a device that is relevant. This is probably not really necessary, but this check appears in many drivers.

Accessing registers

Next, the memory segment is allocated and mapped into virtual memory:

  int rc = 0;
  struct resource res;
  void *registers;

  rc = of_address_to_resource(&op->dev.of_node, 0, &res);
  if (rc) {
    /* Fail */
  }

  if  (!request_mem_region(res.start, resource_size(&res), "xillybus")) {
    /* Fail */
  }

  registers = of_iomap(op->dev.of_node, 0);

  if (!registers) {
    /* Fail */
  }

of_address_to_resource() populates the “res” structure with the memory segment given by the first “reg” assignment (hence the second argument = 0) in the peripheral’s device tree entry. In our example it’s “reg = < 0x50000000 0x1000 >”, meaning that the allocated chunk starts at physical address 0x50000000 and has the size of 0x1000 bytes. of_address_to_resource() will therefore set res.start = 0x50000000 and res.end = 0x50000fff.

In the absence of an automatic tool for Zynq, the address and size should be copied manually from the address map defined in XPS (click the “Addresses” tab XPS’ main window).

Then request_mem_region() is called in order to register the specific memory segment, like any device driver. The purpose is just to avoid clashes between two drivers accessing the same register space (which should never happen anyhow). The resource_size() inline function returns the size of the segment, as one would expect (0x1000 in this case).

The of_iomap() function is a combination of of_address_to_resource() and ioremap(), and is essentially equivalent to ioremap(res.start, resource_size(&res)). It makes sure that the physical memory segment has a virtual memory mapping, and returns the virtual address of the beginning of that segment.

Needless to say, these operations need to be reverted before the module is removed from the kernel or if an error occurs later on.

It may be tempting to use the “register” pointer just like any pointer, or “better” still, a pointer to volatile. The rule of thumb in Linux kernel programming is that if you feel tempted to use the “volatile” keyword, you’re doing something wrong: The correct way to access hardware registers is with iowrite32(), ioread32() and other io-something functions and macros. All device drivers demonstrate this.

Note that even though nothing will crash when attempting to access the hardware registers by using the “register” variable as a plain pointer, this is likely to lead to cache coherency problems, in particular on an ARM platform like the Zynq EPP.

Attaching the interrupt handler

The driver’s side to this is quite simple. Something like this:

  irq = irq_of_parse_and_map(op->dev.of_node, 0);

  rc = request_irq(irq, xillybus_isr, 0, "xillybus", op->dev);

The irq_of_parse_and_map() call merely looks up the interrupt’s specification in the device tree (more about this below) and returns its identifying number, as request_irq() expects to have it (”irq” matches the enumeration in /proc/interrupts as well). The second argument, zero, says that the first interrupt given in the device tree should be taken.

And then request_irq() registers the interrupt handler. This function is explained in the LDD3 book.

The device tree declaration goes something like (copied from above):

      interrupts = < 0 59 1 >;
      interrupt-parent = <&gic>;

So what are these three numbers assigned to “interrupt”?

The first number (zero) is a flag indicating if the interrupt is an SPI (shared peripheral interrupt). A nonzero value means it is an SPI. The truth is that these interrupts are SPIs according to Zynq’s Technical Reference Manual (the TRM), and still the common convention is to write zero in this field, saying that they aren’t. Since this misdeclaration is so common, it’s recommended to stick to it, in particular since declaring the interrupt as an SPI will cause some confusion regarding the interrupt number. This is discussed in detail here.

The second number is related to the interrupt number. To make a long story short, click the “GIC” box in XPS’ main window’s “Zynq” tab, look up the number assigned to the interrupt (91 for xillybus in Xillinux) and subtract it by 32 (91 - 32 = 59).

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

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

Other values are not allowed. That is, falling edge and active low are not supported, as the hardware doesn’t support those modes. If you need these, put a NOT gate in the logic.

It’s notable that the third number is often zero in “official” device trees, so the Linux kernel leaves the interrupt mode to whatever it was already set to. This usually means active high level triggering, and still, this makes the Linux driver depend on that the boot loader didn’t mess up.

Finally, the interrupt-parent assignment. It should always point to the interrupt controller, which is referenced by &gic. On device trees that were reverse compiled from a DTB file, a number will appear instead of this reference, typically 0x1.


Continue to Part V, which discusses application-specific data in the device tree.