From 12bcf4e4a9d86a7957ffae4c1f026792ca4b458d Mon Sep 17 00:00:00 2001 From: Joris van Rantwijk Date: Fri, 2 Aug 2024 21:02:17 +0200 Subject: [PATCH] Add Linux driver for PuzzleFW firmware --- os/15_build_driver.sh | 13 ++ os/src/linux_driver/.gitignore | 7 + os/src/linux_driver/Makefile | 16 +++ os/src/linux_driver/puzzlefw.c | 256 +++++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100755 os/15_build_driver.sh create mode 100644 os/src/linux_driver/.gitignore create mode 100644 os/src/linux_driver/Makefile create mode 100644 os/src/linux_driver/puzzlefw.c diff --git a/os/15_build_driver.sh b/os/15_build_driver.sh new file mode 100755 index 0000000..4a1f997 --- /dev/null +++ b/os/15_build_driver.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +. script_env +setup_toolchain + +KDIR="$(realpath -m "$LINUX_DIR")" + +cd src/linux_driver +make KDIR="$KDIR" ARCH=arm CROSS_COMPILE=arm-linux- clean +make KDIR="$KDIR" ARCH=arm CROSS_COMPILE=arm-linux- modules + diff --git a/os/src/linux_driver/.gitignore b/os/src/linux_driver/.gitignore new file mode 100644 index 0000000..156dffa --- /dev/null +++ b/os/src/linux_driver/.gitignore @@ -0,0 +1,7 @@ +*.o +*.ko +*.mod +*.mod.c +.*.cmd +Module.symvers +modules.order diff --git a/os/src/linux_driver/Makefile b/os/src/linux_driver/Makefile new file mode 100644 index 0000000..ea3dda0 --- /dev/null +++ b/os/src/linux_driver/Makefile @@ -0,0 +1,16 @@ + +# +# To build this driver, you need a Linux kernel source tree, +# fully configured and built. Set KDIR to point to that tree. +# + +obj-m += puzzlefw.o + +.PHONY: modules +modules: + $(MAKE) -C $(KDIR) M=$(CURDIR) modules + +.PHONY: clean +clean: + $(MAKE) -C $(KDIR) M=$(CURDIR) clean + diff --git a/os/src/linux_driver/puzzlefw.c b/os/src/linux_driver/puzzlefw.c new file mode 100644 index 0000000..ae518a3 --- /dev/null +++ b/os/src/linux_driver/puzzlefw.c @@ -0,0 +1,256 @@ +/* + * puzzlefw.c + * + * Device driver for the RedPitaya PuzzleFW firmware. + * + * Written in 2024 by Joris van Rantwijk. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DRIVER_NAME "puzzlefw" +#define PUZZLEFW_FW_API 0x4a01 + +#define USERREG_INFO 0x00 +#define USERREG_IRQ_ENABLE 0x10 +#define PRIVREG_DMA_BUF_ADDR 0x00 +#define PRIVREG_DMA_BUF_SIZE 0x04 + + +/* Device-specific data. */ +struct puzzlefw_drvdata { + struct platform_device * pdev; + struct uio_info * uioinfo; + void __iomem * userregs; + void __iomem * privregs; + dma_addr_t dma_buf_addr; + resource_size_t dma_buf_size; + char version_str[16]; +}; + + +/* Called by UIO framework when an interrupt occurs. */ +static irqreturn_t puzzlefw_irq_handler(int irq, struct uio_info *uioinfo) +{ + struct puzzlefw_drvdata *d = uioinfo->priv; + + /* Disable interrupts. The user-space driver is responsible + for re-enabling interrupts after handling this interrupt. */ + writel(0, d->userregs + USERREG_IRQ_ENABLE); + + return IRQ_HANDLED; +} + + +/* + * Detect FPGA firmware. + * + * Note this function reads from the APB registers in the FPGA. + * This will cause the system to hang if the FPGA image is + * not loaded or not functional. + */ +static int puzzlefw_detect(struct puzzlefw_drvdata *d) +{ + uint32_t fw_info; + unsigned int fw_api, fw_version_major, fw_version_minor; + + dev_dbg(&d->pdev->dev, "probing firmware\n"); + + fw_info = readl(d->userregs + USERREG_INFO); + fw_api = fw_info >> 16; + fw_version_major = (fw_info >> 8) & 0xff; + fw_version_minor = fw_info & 0xff; + + if (fw_api != PUZZLEFW_FW_API) { + dev_err(&d->pdev->dev, + "unrecognized firmware info register 0x%08x\n", + fw_info); + return -ENODEV; + } + + dev_info(&d->pdev->dev, + "detected PuzzleFW firmware version %u.%u\n", + fw_version_major, fw_version_minor); + + snprintf(d->version_str, sizeof(d->version_str), + "%u.%u", fw_version_major, fw_version_minor); + + return 0; +} + + +/* Write address and size of DMA buffer in FPGA registers. */ +static void puzzlefw_set_dma_window( + struct puzzlefw_drvdata *d, + dma_addr_t buf_addr, + size_t buf_size) +{ + writel(0, d->privregs + PRIVREG_DMA_BUF_SIZE); + writel(buf_addr, d->privregs + PRIVREG_DMA_BUF_ADDR); + writel(buf_size, d->privregs + PRIVREG_DMA_BUF_SIZE); +} + + +/* Register device instance. */ +static int puzzlefw_probe(struct platform_device *pdev) +{ + struct puzzlefw_drvdata *drvdata; + struct device_node *mem_node; + struct resource mem_res; + struct resource *user_regs; + struct uio_info *uioinfo; + int irq; + int ret; + + /* Allocate device-specific data. + Will be released automatically when the driver detaches. */ + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) { + dev_err(&pdev->dev, "alloc drvdata failed\n"); + return -ENOMEM; + } + + drvdata->pdev = pdev; + platform_set_drvdata(pdev, drvdata); + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "missing platform data\n"); + return -EINVAL; + } + + mem_node = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); + if (!mem_node) { + dev_err(&pdev->dev, "missing memory-region\n"); + return -EINVAL; + } + + ret = of_address_to_resource(mem_node, 0, &mem_res); + of_node_put(mem_node); + if (ret) { + dev_err(&pdev->dev, "failed to get memory-region resource\n"); + return ret; + } + + drvdata->dma_buf_addr = mem_res.start; + drvdata->dma_buf_size = resource_size(&mem_res); + + /* Get register user register range from device tree. */ + user_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!user_regs) { + dev_err(&pdev->dev, "no resource in platform data\n"); + return -EINVAL; + } + + /* Get IRQ from device tree. */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "missing interrupt in platform data\n"); + return irq; + } + + /* Map register ranges for access by kernel driver. */ + drvdata->userregs = devm_platform_ioremap_resource(pdev, 0); + drvdata->privregs = devm_platform_ioremap_resource(pdev, 1); + + /* Detect firmware. */ + ret = puzzlefw_detect(drvdata); + if (ret) { + return ret; + } + + /* Register UIO device. */ + uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo), GFP_KERNEL); + if (!uioinfo) { + dev_err(&pdev->dev, "alloc uioinfo failed\n"); + return -ENOMEM; + } + + drvdata->uioinfo = uioinfo; + + uioinfo->name = pdev->name; + uioinfo->version = drvdata->version_str; + + /* Non-privileged registers can be accessed from user space via mmap. */ + uioinfo->mem[0].memtype = UIO_MEM_PHYS; + uioinfo->mem[0].addr = user_regs->start; + uioinfo->mem[0].size = resource_size(user_regs); + uioinfo->mem[0].name = "registers"; + + /* DMA buffer can be accessed from user space via mmap. */ + uioinfo->mem[1].memtype = UIO_MEM_PHYS; + uioinfo->mem[1].addr = drvdata->dma_buf_addr; + uioinfo->mem[1].size = drvdata->dma_buf_size; + uioinfo->mem[1].name = "dma"; + + /* IRQ managed by user space. */ + uioinfo->irq = irq; + uioinfo->irq_flags = 0; /* IRQ not shared */ + + uioinfo->handler = puzzlefw_irq_handler; + uioinfo->irqcontrol = NULL; + uioinfo->open = NULL; + uioinfo->release = NULL; + + uioinfo->priv = drvdata; + + ret = devm_uio_register_device(&pdev->dev, uioinfo); + if (ret) { + dev_err(&pdev->dev, "uio_register_device failed\n"); + return ret; + } + + dev_info(&pdev->dev, "%lu bytes DMA buffer\n", + (unsigned long)drvdata->dma_buf_size); + + /* Set up privileged FPGA registers to access the DMA buffer. */ + puzzlefw_set_dma_window(drvdata, + drvdata->dma_buf_addr, drvdata->dma_buf_size); + + return 0; +} + + +/* Unregister device instance. */ +static int puzzlefw_remove(struct platform_device *pdev) +{ + struct puzzlefw_drvdata *drvdata = platform_get_drvdata(pdev); + + /* Block FPGA access to DMA buffer. */ + puzzlefw_set_dma_window(drvdata, drvdata->dma_buf_addr, 0); + + return 0; +} + + +static const struct of_device_id puzzlefw_of_match[] = { + { .compatible = "jigsaw,puzzlefw" }, + {}, +}; + + +static struct platform_driver puzzlefw_platform_driver = { + .probe = puzzlefw_probe, + .remove = puzzlefw_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = puzzlefw_of_match, + }, +}; + + +module_platform_driver(puzzlefw_platform_driver); + +MODULE_AUTHOR("Joris van Rantwijk"); +MODULE_DESCRIPTION("RedPitaya-PuzzleFW driver"); +MODULE_LICENSE("GPL"); +