Skip to content

FPGA

AXI Memory Access Example

The following is a full driver example that exposes a specific 32 bit read only register in an AXI IP to the sysfs interface.

Warning

This compiles on Ubuntu 20.04 but is untested on hardware.

``// SPDX-License-Identifier: GPL-2.0

#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>

// Automatically add driver and function name to printk
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) "%s:%s: " fmt "\n", KBUILD_MODNAME, __func__

// Memory
static void __iomem *virt_mem_base;

// Device tree info
static struct resource dt_resource;
#define ADDRESS_BASE phys_to_virt(dt_resource.start)

// sysfs
static struct class *example_class;
static struct device *example_device;

// # "module": the module name
static ssize_t module_show(struct device *dev, struct device_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%s\n", KBUILD_MODNAME);
}
static DEVICE_ATTR_RO(module);

// # "my_register": the 32 bit unsigned value at address offset 0
#define ADDRESS_MY_REGISTER ADDRESS_BASE + 0
static ssize_t my_register_show(struct device *dev, struct device_attribute *attr, char *buf) {
    return sysfs_emit(buf, "%d\n", readl(ADDRESS_MY_REGISTER));
}
static DEVICE_ATTR_RO(my_register);

static int __init example_init(void)
{
    int ret;

    // Get device tree information
    struct device_node *node = of_find_node_by_name(NULL, "my_axi_ip_name");
    if (!node) {
        pr_err("failed to get device tree for my_axi_ip_name");
        ret = -ENODEV;
        goto just_return;
    }
    ret = of_address_to_resource(node, 0, &dt_resource);
    if (ret) {
        pr_err("failed to get device tree for my_axi_ip_name (%d)", ret);
        goto destroy_device_tree_node;
    }

    // Get virtual memory
    if (!request_mem_region(dt_resource.start, resource_size(&dt_resource), KBUILD_MODNAME))
    {
        pr_err("failed to request memory region");
        ret = -EBUSY;
        goto destroy_device_tree_node;
    }
    virt_mem_base = ioremap(dt_resource.start, resource_size(&dt_resource));
    if (!virt_mem_base) {
        pr_err("failed to ioremap physical memory region");
        ret = -ENOMEM;
        goto release_memory;
    }

    // Create sysfs interface
    example_class = class_create(THIS_MODULE, KBUILD_MODNAME);
    example_device = device_create(example_class,
                                   NULL,
                                   0,
                                   NULL,
                                   KBUILD_MODNAME);
    ret = device_create_file(example_device, &dev_attr_module);
    if (ret) {
        pr_err("failed to create device file for 'module' (%d)", ret);
        goto destroy_device_and_class;
    }
    ret = device_create_file(example_device, &dev_attr_my_register);
    if (ret) {
        pr_err("failed to create device file for 'my_register' (%d)", ret);
        goto destroy_device_and_class;
    }

    of_node_put(node);
    return 0;

destroy_device_and_class:
    device_destroy(example_class, 0);
    class_destroy(example_class);
release_memory:
    release_mem_region(dt_resource.start, resource_size(&dt_resource));
destroy_device_tree_node:
    of_node_put(node);
just_return:
    return ret;
}

static void __exit example_exit(void)
{
    device_remove_file(example_device, &dev_attr_my_register);
    device_remove_file(example_device, &dev_attr_module);
    device_destroy(example_class, 0);
    class_destroy(example_class);
}

module_init(example_init);
module_exit(example_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
MODULE_DESCRIPTION("");

References

  • ROACH Memory Map
    • This uses a character device to expose the memory region for direct read and write.
    • It also performs the FPGA bitstream loading itself.