diff options
author | Linus Torvalds | 2018-08-18 20:04:51 +0200 |
---|---|---|
committer | Linus Torvalds | 2018-08-18 20:04:51 +0200 |
commit | d5acba26bfa097a618be425522b1ec4269d3edaf (patch) | |
tree | 7abb08032d4b79b34eb1386aa007a811e1964839 /drivers/fpga | |
parent | Merge tag 'staging-4.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git... (diff) | |
parent | android: binder: Rate-limit debug and userspace triggered err msgs (diff) | |
download | kernel-qcow2-linux-d5acba26bfa097a618be425522b1ec4269d3edaf.tar.gz kernel-qcow2-linux-d5acba26bfa097a618be425522b1ec4269d3edaf.tar.xz kernel-qcow2-linux-d5acba26bfa097a618be425522b1ec4269d3edaf.zip |
Merge tag 'char-misc-4.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc driver updates from Greg KH:
"Here is the bit set of char/misc drivers for 4.19-rc1
There is a lot here, much more than normal, seems like everyone is
writing new driver subsystems these days... Anyway, major things here
are:
- new FSI driver subsystem, yet-another-powerpc low-level hardware
bus
- gnss, finally an in-kernel GPS subsystem to try to tame all of the
crazy out-of-tree drivers that have been floating around for years,
combined with some really hacky userspace implementations. This is
only for GNSS receivers, but you have to start somewhere, and this
is great to see.
Other than that, there are new slimbus drivers, new coresight drivers,
new fpga drivers, and loads of DT bindings for all of these and
existing drivers.
All of these have been in linux-next for a while with no reported
issues"
* tag 'char-misc-4.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (255 commits)
android: binder: Rate-limit debug and userspace triggered err msgs
fsi: sbefifo: Bump max command length
fsi: scom: Fix NULL dereference
misc: mic: SCIF Fix scif_get_new_port() error handling
misc: cxl: changed asterisk position
genwqe: card_base: Use true and false for boolean values
misc: eeprom: assignment outside the if statement
uio: potential double frees if __uio_register_device() fails
eeprom: idt_89hpesx: clean up an error pointer vs NULL inconsistency
misc: ti-st: Fix memory leak in the error path of probe()
android: binder: Show extra_buffers_size in trace
firmware: vpd: Fix section enabled flag on vpd_section_destroy
platform: goldfish: Retire pdev_bus
goldfish: Use dedicated macros instead of manual bit shifting
goldfish: Add missing includes to goldfish.h
mux: adgs1408: new driver for Analog Devices ADGS1408/1409 mux
dt-bindings: mux: add adi,adgs1408
Drivers: hv: vmbus: Cleanup synic memory free path
Drivers: hv: vmbus: Remove use of slow_virt_to_phys()
Drivers: hv: vmbus: Reset the channel callback in vmbus_onoffer_rescind()
...
Diffstat (limited to 'drivers/fpga')
-rw-r--r-- | drivers/fpga/Kconfig | 68 | ||||
-rw-r--r-- | drivers/fpga/Makefile | 14 | ||||
-rw-r--r-- | drivers/fpga/dfl-afu-dma-region.c | 463 | ||||
-rw-r--r-- | drivers/fpga/dfl-afu-main.c | 636 | ||||
-rw-r--r-- | drivers/fpga/dfl-afu-region.c | 166 | ||||
-rw-r--r-- | drivers/fpga/dfl-afu.h | 100 | ||||
-rw-r--r-- | drivers/fpga/dfl-fme-br.c | 114 | ||||
-rw-r--r-- | drivers/fpga/dfl-fme-main.c | 279 | ||||
-rw-r--r-- | drivers/fpga/dfl-fme-mgr.c | 349 | ||||
-rw-r--r-- | drivers/fpga/dfl-fme-pr.c | 479 | ||||
-rw-r--r-- | drivers/fpga/dfl-fme-pr.h | 84 | ||||
-rw-r--r-- | drivers/fpga/dfl-fme-region.c | 89 | ||||
-rw-r--r-- | drivers/fpga/dfl-fme.h | 38 | ||||
-rw-r--r-- | drivers/fpga/dfl-pci.c | 243 | ||||
-rw-r--r-- | drivers/fpga/dfl.c | 1044 | ||||
-rw-r--r-- | drivers/fpga/dfl.h | 410 | ||||
-rw-r--r-- | drivers/fpga/fpga-mgr.c | 28 | ||||
-rw-r--r-- | drivers/fpga/fpga-region.c | 22 |
18 files changed, 4626 insertions, 0 deletions
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index ee9c5420c47f..1ebcef4bab5b 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -130,4 +130,72 @@ config OF_FPGA_REGION Support for loading FPGA images by applying a Device Tree overlay. +config FPGA_DFL + tristate "FPGA Device Feature List (DFL) support" + select FPGA_BRIDGE + select FPGA_REGION + help + Device Feature List (DFL) defines a feature list structure that + creates a linked list of feature headers within the MMIO space + to provide an extensible way of adding features for FPGA. + Driver can walk through the feature headers to enumerate feature + devices (e.g. FPGA Management Engine, Port and Accelerator + Function Unit) and their private features for target FPGA devices. + + Select this option to enable common support for Field-Programmable + Gate Array (FPGA) solutions which implement Device Feature List. + It provides enumeration APIs and feature device infrastructure. + +config FPGA_DFL_FME + tristate "FPGA DFL FME Driver" + depends on FPGA_DFL + help + The FPGA Management Engine (FME) is a feature device implemented + under Device Feature List (DFL) framework. Select this option to + enable the platform device driver for FME which implements all + FPGA platform level management features. There shall be one FME + per DFL based FPGA device. + +config FPGA_DFL_FME_MGR + tristate "FPGA DFL FME Manager Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Manager driver for FPGA Management Engine. + +config FPGA_DFL_FME_BRIDGE + tristate "FPGA DFL FME Bridge Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Bridge driver for FPGA Management Engine. + +config FPGA_DFL_FME_REGION + tristate "FPGA DFL FME Region Driver" + depends on FPGA_DFL_FME && HAS_IOMEM + help + Say Y to enable FPGA Region driver for FPGA Management Engine. + +config FPGA_DFL_AFU + tristate "FPGA DFL AFU Driver" + depends on FPGA_DFL + help + This is the driver for FPGA Accelerated Function Unit (AFU) which + implements AFU and Port management features. A User AFU connects + to the FPGA infrastructure via a Port. There may be more than one + Port/AFU per DFL based FPGA device. + +config FPGA_DFL_PCI + tristate "FPGA DFL PCIe Device Driver" + depends on PCI && FPGA_DFL + help + Select this option to enable PCIe driver for PCIe-based + Field-Programmable Gate Array (FPGA) solutions which implement + the Device Feature List (DFL). This driver provides interfaces + for userspace applications to configure, enumerate, open and access + FPGA accelerators on the FPGA DFL devices, enables system level + management functions such as FPGA partial reconfiguration, power + management and virtualization with DFL framework and DFL feature + device drivers. + + To compile this as a module, choose M here. + endif # FPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index f9803dad6919..7a2d73ba7122 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -28,3 +28,17 @@ obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o # High Level Interfaces obj-$(CONFIG_FPGA_REGION) += fpga-region.o obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o + +# FPGA Device Feature List Support +obj-$(CONFIG_FPGA_DFL) += dfl.o +obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o +obj-$(CONFIG_FPGA_DFL_FME_MGR) += dfl-fme-mgr.o +obj-$(CONFIG_FPGA_DFL_FME_BRIDGE) += dfl-fme-br.o +obj-$(CONFIG_FPGA_DFL_FME_REGION) += dfl-fme-region.o +obj-$(CONFIG_FPGA_DFL_AFU) += dfl-afu.o + +dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o +dfl-afu-objs := dfl-afu-main.o dfl-afu-region.o dfl-afu-dma-region.o + +# Drivers for FPGAs which implement DFL +obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-afu-dma-region.c b/drivers/fpga/dfl-afu-dma-region.c new file mode 100644 index 000000000000..0e81d33af856 --- /dev/null +++ b/drivers/fpga/dfl-afu-dma-region.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) DMA Region Management + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ + +#include <linux/dma-mapping.h> +#include <linux/sched/signal.h> +#include <linux/uaccess.h> + +#include "dfl-afu.h" + +static void put_all_pages(struct page **pages, int npages) +{ + int i; + + for (i = 0; i < npages; i++) + if (pages[i]) + put_page(pages[i]); +} + +void afu_dma_region_init(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + + afu->dma_regions = RB_ROOT; +} + +/** + * afu_dma_adjust_locked_vm - adjust locked memory + * @dev: port device + * @npages: number of pages + * @incr: increase or decrease locked memory + * + * Increase or decrease the locked memory size with npages input. + * + * Return 0 on success. + * Return -ENOMEM if locked memory size is over the limit and no CAP_IPC_LOCK. + */ +static int afu_dma_adjust_locked_vm(struct device *dev, long npages, bool incr) +{ + unsigned long locked, lock_limit; + int ret = 0; + + /* the task is exiting. */ + if (!current->mm) + return 0; + + down_write(¤t->mm->mmap_sem); + + if (incr) { + locked = current->mm->locked_vm + npages; + lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; + + if (locked > lock_limit && !capable(CAP_IPC_LOCK)) + ret = -ENOMEM; + else + current->mm->locked_vm += npages; + } else { + if (WARN_ON_ONCE(npages > current->mm->locked_vm)) + npages = current->mm->locked_vm; + current->mm->locked_vm -= npages; + } + + dev_dbg(dev, "[%d] RLIMIT_MEMLOCK %c%ld %ld/%ld%s\n", current->pid, + incr ? '+' : '-', npages << PAGE_SHIFT, + current->mm->locked_vm << PAGE_SHIFT, rlimit(RLIMIT_MEMLOCK), + ret ? "- execeeded" : ""); + + up_write(¤t->mm->mmap_sem); + + return ret; +} + +/** + * afu_dma_pin_pages - pin pages of given dma memory region + * @pdata: feature device platform data + * @region: dma memory region to be pinned + * + * Pin all the pages of given dfl_afu_dma_region. + * Return 0 for success or negative error code. + */ +static int afu_dma_pin_pages(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + int npages = region->length >> PAGE_SHIFT; + struct device *dev = &pdata->dev->dev; + int ret, pinned; + + ret = afu_dma_adjust_locked_vm(dev, npages, true); + if (ret) + return ret; + + region->pages = kcalloc(npages, sizeof(struct page *), GFP_KERNEL); + if (!region->pages) { + ret = -ENOMEM; + goto unlock_vm; + } + + pinned = get_user_pages_fast(region->user_addr, npages, 1, + region->pages); + if (pinned < 0) { + ret = pinned; + goto put_pages; + } else if (pinned != npages) { + ret = -EFAULT; + goto free_pages; + } + + dev_dbg(dev, "%d pages pinned\n", pinned); + + return 0; + +put_pages: + put_all_pages(region->pages, pinned); +free_pages: + kfree(region->pages); +unlock_vm: + afu_dma_adjust_locked_vm(dev, npages, false); + return ret; +} + +/** + * afu_dma_unpin_pages - unpin pages of given dma memory region + * @pdata: feature device platform data + * @region: dma memory region to be unpinned + * + * Unpin all the pages of given dfl_afu_dma_region. + * Return 0 for success or negative error code. + */ +static void afu_dma_unpin_pages(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + long npages = region->length >> PAGE_SHIFT; + struct device *dev = &pdata->dev->dev; + + put_all_pages(region->pages, npages); + kfree(region->pages); + afu_dma_adjust_locked_vm(dev, npages, false); + + dev_dbg(dev, "%ld pages unpinned\n", npages); +} + +/** + * afu_dma_check_continuous_pages - check if pages are continuous + * @region: dma memory region + * + * Return true if pages of given dma memory region have continuous physical + * address, otherwise return false. + */ +static bool afu_dma_check_continuous_pages(struct dfl_afu_dma_region *region) +{ + int npages = region->length >> PAGE_SHIFT; + int i; + + for (i = 0; i < npages - 1; i++) + if (page_to_pfn(region->pages[i]) + 1 != + page_to_pfn(region->pages[i + 1])) + return false; + + return true; +} + +/** + * dma_region_check_iova - check if memory area is fully contained in the region + * @region: dma memory region + * @iova: address of the dma memory area + * @size: size of the dma memory area + * + * Compare the dma memory area defined by @iova and @size with given dma region. + * Return true if memory area is fully contained in the region, otherwise false. + */ +static bool dma_region_check_iova(struct dfl_afu_dma_region *region, + u64 iova, u64 size) +{ + if (!size && region->iova != iova) + return false; + + return (region->iova <= iova) && + (region->length + region->iova >= iova + size); +} + +/** + * afu_dma_region_add - add given dma region to rbtree + * @pdata: feature device platform data + * @region: dma region to be added + * + * Return 0 for success, -EEXIST if dma region has already been added. + * + * Needs to be called with pdata->lock heold. + */ +static int afu_dma_region_add(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node **new, *parent = NULL; + + dev_dbg(&pdata->dev->dev, "add region (iova = %llx)\n", + (unsigned long long)region->iova); + + new = &afu->dma_regions.rb_node; + + while (*new) { + struct dfl_afu_dma_region *this; + + this = container_of(*new, struct dfl_afu_dma_region, node); + + parent = *new; + + if (dma_region_check_iova(this, region->iova, region->length)) + return -EEXIST; + + if (region->iova < this->iova) + new = &((*new)->rb_left); + else if (region->iova > this->iova) + new = &((*new)->rb_right); + else + return -EEXIST; + } + + rb_link_node(®ion->node, parent, new); + rb_insert_color(®ion->node, &afu->dma_regions); + + return 0; +} + +/** + * afu_dma_region_remove - remove given dma region from rbtree + * @pdata: feature device platform data + * @region: dma region to be removed + * + * Needs to be called with pdata->lock heold. + */ +static void afu_dma_region_remove(struct dfl_feature_platform_data *pdata, + struct dfl_afu_dma_region *region) +{ + struct dfl_afu *afu; + + dev_dbg(&pdata->dev->dev, "del region (iova = %llx)\n", + (unsigned long long)region->iova); + + afu = dfl_fpga_pdata_get_private(pdata); + rb_erase(®ion->node, &afu->dma_regions); +} + +/** + * afu_dma_region_destroy - destroy all regions in rbtree + * @pdata: feature device platform data + * + * Needs to be called with pdata->lock heold. + */ +void afu_dma_region_destroy(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node *node = rb_first(&afu->dma_regions); + struct dfl_afu_dma_region *region; + + while (node) { + region = container_of(node, struct dfl_afu_dma_region, node); + + dev_dbg(&pdata->dev->dev, "del region (iova = %llx)\n", + (unsigned long long)region->iova); + + rb_erase(node, &afu->dma_regions); + + if (region->iova) + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, + DMA_BIDIRECTIONAL); + + if (region->pages) + afu_dma_unpin_pages(pdata, region); + + node = rb_next(node); + kfree(region); + } +} + +/** + * afu_dma_region_find - find the dma region from rbtree based on iova and size + * @pdata: feature device platform data + * @iova: address of the dma memory area + * @size: size of the dma memory area + * + * It finds the dma region from the rbtree based on @iova and @size: + * - if @size == 0, it finds the dma region which starts from @iova + * - otherwise, it finds the dma region which fully contains + * [@iova, @iova+size) + * If nothing is matched returns NULL. + * + * Needs to be called with pdata->lock held. + */ +struct dfl_afu_dma_region * +afu_dma_region_find(struct dfl_feature_platform_data *pdata, u64 iova, u64 size) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct rb_node *node = afu->dma_regions.rb_node; + struct device *dev = &pdata->dev->dev; + + while (node) { + struct dfl_afu_dma_region *region; + + region = container_of(node, struct dfl_afu_dma_region, node); + + if (dma_region_check_iova(region, iova, size)) { + dev_dbg(dev, "find region (iova = %llx)\n", + (unsigned long long)region->iova); + return region; + } + + if (iova < region->iova) + node = node->rb_left; + else if (iova > region->iova) + node = node->rb_right; + else + /* the iova region is not fully covered. */ + break; + } + + dev_dbg(dev, "region with iova %llx and size %llx is not found\n", + (unsigned long long)iova, (unsigned long long)size); + + return NULL; +} + +/** + * afu_dma_region_find_iova - find the dma region from rbtree by iova + * @pdata: feature device platform data + * @iova: address of the dma region + * + * Needs to be called with pdata->lock held. + */ +static struct dfl_afu_dma_region * +afu_dma_region_find_iova(struct dfl_feature_platform_data *pdata, u64 iova) +{ + return afu_dma_region_find(pdata, iova, 0); +} + +/** + * afu_dma_map_region - map memory region for dma + * @pdata: feature device platform data + * @user_addr: address of the memory region + * @length: size of the memory region + * @iova: pointer of iova address + * + * Map memory region defined by @user_addr and @length, and return dma address + * of the memory region via @iova. + * Return 0 for success, otherwise error code. + */ +int afu_dma_map_region(struct dfl_feature_platform_data *pdata, + u64 user_addr, u64 length, u64 *iova) +{ + struct dfl_afu_dma_region *region; + int ret; + + /* + * Check Inputs, only accept page-aligned user memory region with + * valid length. + */ + if (!PAGE_ALIGNED(user_addr) || !PAGE_ALIGNED(length) || !length) + return -EINVAL; + + /* Check overflow */ + if (user_addr + length < user_addr) + return -EINVAL; + + if (!access_ok(VERIFY_WRITE, (void __user *)(unsigned long)user_addr, + length)) + return -EINVAL; + + region = kzalloc(sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + region->user_addr = user_addr; + region->length = length; + + /* Pin the user memory region */ + ret = afu_dma_pin_pages(pdata, region); + if (ret) { + dev_err(&pdata->dev->dev, "failed to pin memory region\n"); + goto free_region; + } + + /* Only accept continuous pages, return error else */ + if (!afu_dma_check_continuous_pages(region)) { + dev_err(&pdata->dev->dev, "pages are not continuous\n"); + ret = -EINVAL; + goto unpin_pages; + } + + /* As pages are continuous then start to do DMA mapping */ + region->iova = dma_map_page(dfl_fpga_pdata_to_parent(pdata), + region->pages[0], 0, + region->length, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(&pdata->dev->dev, region->iova)) { + dev_err(&pdata->dev->dev, "failed to map for dma\n"); + ret = -EFAULT; + goto unpin_pages; + } + + *iova = region->iova; + + mutex_lock(&pdata->lock); + ret = afu_dma_region_add(pdata, region); + mutex_unlock(&pdata->lock); + if (ret) { + dev_err(&pdata->dev->dev, "failed to add dma region\n"); + goto unmap_dma; + } + + return 0; + +unmap_dma: + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, DMA_BIDIRECTIONAL); +unpin_pages: + afu_dma_unpin_pages(pdata, region); +free_region: + kfree(region); + return ret; +} + +/** + * afu_dma_unmap_region - unmap dma memory region + * @pdata: feature device platform data + * @iova: dma address of the region + * + * Unmap dma memory region based on @iova. + * Return 0 for success, otherwise error code. + */ +int afu_dma_unmap_region(struct dfl_feature_platform_data *pdata, u64 iova) +{ + struct dfl_afu_dma_region *region; + + mutex_lock(&pdata->lock); + region = afu_dma_region_find_iova(pdata, iova); + if (!region) { + mutex_unlock(&pdata->lock); + return -EINVAL; + } + + if (region->in_use) { + mutex_unlock(&pdata->lock); + return -EBUSY; + } + + afu_dma_region_remove(pdata, region); + mutex_unlock(&pdata->lock); + + dma_unmap_page(dfl_fpga_pdata_to_parent(pdata), + region->iova, region->length, DMA_BIDIRECTIONAL); + afu_dma_unpin_pages(pdata, region); + kfree(region); + + return 0; +} diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c new file mode 100644 index 000000000000..02baa6a227c0 --- /dev/null +++ b/drivers/fpga/dfl-afu-main.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <linux/fpga-dfl.h> + +#include "dfl-afu.h" + +/** + * port_enable - enable a port + * @pdev: port platform device. + * + * Enable Port by clear the port soft reset bit, which is set by default. + * The AFU is unable to respond to any MMIO access while in reset. + * port_enable function should only be used after port_disable function. + */ +static void port_enable(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __iomem *base; + u64 v; + + WARN_ON(!pdata->disable_count); + + if (--pdata->disable_count != 0) + return; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + /* Clear port soft reset */ + v = readq(base + PORT_HDR_CTRL); + v &= ~PORT_CTRL_SFTRST; + writeq(v, base + PORT_HDR_CTRL); +} + +#define RST_POLL_INVL 10 /* us */ +#define RST_POLL_TIMEOUT 1000 /* us */ + +/** + * port_disable - disable a port + * @pdev: port platform device. + * + * Disable Port by setting the port soft reset bit, it puts the port into + * reset. + */ +static int port_disable(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __iomem *base; + u64 v; + + if (pdata->disable_count++ != 0) + return 0; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + /* Set port soft reset */ + v = readq(base + PORT_HDR_CTRL); + v |= PORT_CTRL_SFTRST; + writeq(v, base + PORT_HDR_CTRL); + + /* + * HW sets ack bit to 1 when all outstanding requests have been drained + * on this port and minimum soft reset pulse width has elapsed. + * Driver polls port_soft_reset_ack to determine if reset done by HW. + */ + if (readq_poll_timeout(base + PORT_HDR_CTRL, v, v & PORT_CTRL_SFTRST, + RST_POLL_INVL, RST_POLL_TIMEOUT)) { + dev_err(&pdev->dev, "timeout, fail to reset device\n"); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * This function resets the FPGA Port and its accelerator (AFU) by function + * __port_disable and __port_enable (set port soft reset bit and then clear + * it). Userspace can do Port reset at any time, e.g. during DMA or Partial + * Reconfiguration. But it should never cause any system level issue, only + * functional failure (e.g. DMA or PR operation failure) and be recoverable + * from the failure. + * + * Note: the accelerator (AFU) is not accessible when its port is in reset + * (disabled). Any attempts on MMIO access to AFU while in reset, will + * result errors reported via port error reporting sub feature (if present). + */ +static int __port_reset(struct platform_device *pdev) +{ + int ret; + + ret = port_disable(pdev); + if (!ret) + port_enable(pdev); + + return ret; +} + +static int port_reset(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret; + + mutex_lock(&pdata->lock); + ret = __port_reset(pdev); + mutex_unlock(&pdata->lock); + + return ret; +} + +static int port_get_id(struct platform_device *pdev) +{ + void __iomem *base; + + base = dfl_get_feature_ioaddr_by_id(&pdev->dev, PORT_FEATURE_ID_HEADER); + + return FIELD_GET(PORT_CAP_PORT_NUM, readq(base + PORT_HDR_CAP)); +} + +static ssize_t +id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + int id = port_get_id(to_platform_device(dev)); + + return scnprintf(buf, PAGE_SIZE, "%d\n", id); +} +static DEVICE_ATTR_RO(id); + +static const struct attribute *port_hdr_attrs[] = { + &dev_attr_id.attr, + NULL, +}; + +static int port_hdr_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT HDR Init.\n"); + + port_reset(pdev); + + return sysfs_create_files(&pdev->dev.kobj, port_hdr_attrs); +} + +static void port_hdr_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT HDR UInit.\n"); + + sysfs_remove_files(&pdev->dev.kobj, port_hdr_attrs); +} + +static long +port_hdr_ioctl(struct platform_device *pdev, struct dfl_feature *feature, + unsigned int cmd, unsigned long arg) +{ + long ret; + + switch (cmd) { + case DFL_FPGA_PORT_RESET: + if (!arg) + ret = port_reset(pdev); + else + ret = -EINVAL; + break; + default: + dev_dbg(&pdev->dev, "%x cmd not handled", cmd); + ret = -ENODEV; + } + + return ret; +} + +static const struct dfl_feature_ops port_hdr_ops = { + .init = port_hdr_init, + .uinit = port_hdr_uinit, + .ioctl = port_hdr_ioctl, +}; + +static ssize_t +afu_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); + void __iomem *base; + u64 guidl, guidh; + + base = dfl_get_feature_ioaddr_by_id(dev, PORT_FEATURE_ID_AFU); + + mutex_lock(&pdata->lock); + if (pdata->disable_count) { + mutex_unlock(&pdata->lock); + return -EBUSY; + } + + guidl = readq(base + GUID_L); + guidh = readq(base + GUID_H); + mutex_unlock(&pdata->lock); + + return scnprintf(buf, PAGE_SIZE, "%016llx%016llx\n", guidh, guidl); +} +static DEVICE_ATTR_RO(afu_id); + +static const struct attribute *port_afu_attrs[] = { + &dev_attr_afu_id.attr, + NULL +}; + +static int port_afu_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct resource *res = &pdev->resource[feature->resource_index]; + int ret; + + dev_dbg(&pdev->dev, "PORT AFU Init.\n"); + + ret = afu_mmio_region_add(dev_get_platdata(&pdev->dev), + DFL_PORT_REGION_INDEX_AFU, resource_size(res), + res->start, DFL_PORT_REGION_READ | + DFL_PORT_REGION_WRITE | DFL_PORT_REGION_MMAP); + if (ret) + return ret; + + return sysfs_create_files(&pdev->dev.kobj, port_afu_attrs); +} + +static void port_afu_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "PORT AFU UInit.\n"); + + sysfs_remove_files(&pdev->dev.kobj, port_afu_attrs); +} + +static const struct dfl_feature_ops port_afu_ops = { + .init = port_afu_init, + .uinit = port_afu_uinit, +}; + +static struct dfl_feature_driver port_feature_drvs[] = { + { + .id = PORT_FEATURE_ID_HEADER, + .ops = &port_hdr_ops, + }, + { + .id = PORT_FEATURE_ID_AFU, + .ops = &port_afu_ops, + }, + { + .ops = NULL, + } +}; + +static int afu_open(struct inode *inode, struct file *filp) +{ + struct platform_device *fdev = dfl_fpga_inode_to_feature_dev(inode); + struct dfl_feature_platform_data *pdata; + int ret; + + pdata = dev_get_platdata(&fdev->dev); + if (WARN_ON(!pdata)) + return -ENODEV; + + ret = dfl_feature_dev_use_begin(pdata); + if (ret) + return ret; + + dev_dbg(&fdev->dev, "Device File Open\n"); + filp->private_data = fdev; + + return 0; +} + +static int afu_release(struct inode *inode, struct file *filp) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + + dev_dbg(&pdev->dev, "Device File Release\n"); + + pdata = dev_get_platdata(&pdev->dev); + + mutex_lock(&pdata->lock); + __port_reset(pdev); + afu_dma_region_destroy(pdata); + mutex_unlock(&pdata->lock); + + dfl_feature_dev_use_end(pdata); + + return 0; +} + +static long afu_ioctl_check_extension(struct dfl_feature_platform_data *pdata, + unsigned long arg) +{ + /* No extension support for now */ + return 0; +} + +static long +afu_ioctl_get_info(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_info info; + struct dfl_afu *afu; + unsigned long minsz; + + minsz = offsetofend(struct dfl_fpga_port_info, num_umsgs); + + if (copy_from_user(&info, arg, minsz)) + return -EFAULT; + + if (info.argsz < minsz) + return -EINVAL; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + info.flags = 0; + info.num_regions = afu->num_regions; + info.num_umsgs = afu->num_umsgs; + mutex_unlock(&pdata->lock); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static long afu_ioctl_get_region_info(struct dfl_feature_platform_data *pdata, + void __user *arg) +{ + struct dfl_fpga_port_region_info rinfo; + struct dfl_afu_mmio_region region; + unsigned long minsz; + long ret; + + minsz = offsetofend(struct dfl_fpga_port_region_info, offset); + + if (copy_from_user(&rinfo, arg, minsz)) + return -EFAULT; + + if (rinfo.argsz < minsz || rinfo.padding) + return -EINVAL; + + ret = afu_mmio_region_get_by_index(pdata, rinfo.index, ®ion); + if (ret) + return ret; + + rinfo.flags = region.flags; + rinfo.size = region.size; + rinfo.offset = region.offset; + + if (copy_to_user(arg, &rinfo, sizeof(rinfo))) + return -EFAULT; + + return 0; +} + +static long +afu_ioctl_dma_map(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_dma_map map; + unsigned long minsz; + long ret; + + minsz = offsetofend(struct dfl_fpga_port_dma_map, iova); + + if (copy_from_user(&map, arg, minsz)) + return -EFAULT; + + if (map.argsz < minsz || map.flags) + return -EINVAL; + + ret = afu_dma_map_region(pdata, map.user_addr, map.length, &map.iova); + if (ret) + return ret; + + if (copy_to_user(arg, &map, sizeof(map))) { + afu_dma_unmap_region(pdata, map.iova); + return -EFAULT; + } + + dev_dbg(&pdata->dev->dev, "dma map: ua=%llx, len=%llx, iova=%llx\n", + (unsigned long long)map.user_addr, + (unsigned long long)map.length, + (unsigned long long)map.iova); + + return 0; +} + +static long +afu_ioctl_dma_unmap(struct dfl_feature_platform_data *pdata, void __user *arg) +{ + struct dfl_fpga_port_dma_unmap unmap; + unsigned long minsz; + + minsz = offsetofend(struct dfl_fpga_port_dma_unmap, iova); + + if (copy_from_user(&unmap, arg, minsz)) + return -EFAULT; + + if (unmap.argsz < minsz || unmap.flags) + return -EINVAL; + + return afu_dma_unmap_region(pdata, unmap.iova); +} + +static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + struct dfl_feature *f; + long ret; + + dev_dbg(&pdev->dev, "%s cmd 0x%x\n", __func__, cmd); + + pdata = dev_get_platdata(&pdev->dev); + + switch (cmd) { + case DFL_FPGA_GET_API_VERSION: + return DFL_FPGA_API_VERSION; + case DFL_FPGA_CHECK_EXTENSION: + return afu_ioctl_check_extension(pdata, arg); + case DFL_FPGA_PORT_GET_INFO: + return afu_ioctl_get_info(pdata, (void __user *)arg); + case DFL_FPGA_PORT_GET_REGION_INFO: + return afu_ioctl_get_region_info(pdata, (void __user *)arg); + case DFL_FPGA_PORT_DMA_MAP: + return afu_ioctl_dma_map(pdata, (void __user *)arg); + case DFL_FPGA_PORT_DMA_UNMAP: + return afu_ioctl_dma_unmap(pdata, (void __user *)arg); + default: + /* + * Let sub-feature's ioctl function to handle the cmd + * Sub-feature's ioctl returns -ENODEV when cmd is not + * handled in this sub feature, and returns 0 and other + * error code if cmd is handled. + */ + dfl_fpga_dev_for_each_feature(pdata, f) + if (f->ops && f->ops->ioctl) { + ret = f->ops->ioctl(pdev, f, cmd, arg); + if (ret != -ENODEV) + return ret; + } + } + + return -EINVAL; +} + +static int afu_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct platform_device *pdev = filp->private_data; + struct dfl_feature_platform_data *pdata; + u64 size = vma->vm_end - vma->vm_start; + struct dfl_afu_mmio_region region; + u64 offset; + int ret; + + if (!(vma->vm_flags & VM_SHARED)) + return -EINVAL; + + pdata = dev_get_platdata(&pdev->dev); + + offset = vma->vm_pgoff << PAGE_SHIFT; + ret = afu_mmio_region_get_by_offset(pdata, offset, size, ®ion); + if (ret) + return ret; + + if (!(region.flags & DFL_PORT_REGION_MMAP)) + return -EINVAL; + + if ((vma->vm_flags & VM_READ) && !(region.flags & DFL_PORT_REGION_READ)) + return -EPERM; + + if ((vma->vm_flags & VM_WRITE) && + !(region.flags & DFL_PORT_REGION_WRITE)) + return -EPERM; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return remap_pfn_range(vma, vma->vm_start, + (region.phys + (offset - region.offset)) >> PAGE_SHIFT, + size, vma->vm_page_prot); +} + +static const struct file_operations afu_fops = { + .owner = THIS_MODULE, + .open = afu_open, + .release = afu_release, + .unlocked_ioctl = afu_ioctl, + .mmap = afu_mmap, +}; + +static int afu_dev_init(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_afu *afu; + + afu = devm_kzalloc(&pdev->dev, sizeof(*afu), GFP_KERNEL); + if (!afu) + return -ENOMEM; + + afu->pdata = pdata; + + mutex_lock(&pdata->lock); + dfl_fpga_pdata_set_private(pdata, afu); + afu_mmio_region_init(pdata); + afu_dma_region_init(pdata); + mutex_unlock(&pdata->lock); + + return 0; +} + +static int afu_dev_destroy(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_afu *afu; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + afu_mmio_region_destroy(pdata); + afu_dma_region_destroy(pdata); + dfl_fpga_pdata_set_private(pdata, NULL); + mutex_unlock(&pdata->lock); + + return 0; +} + +static int port_enable_set(struct platform_device *pdev, bool enable) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret = 0; + + mutex_lock(&pdata->lock); + if (enable) + port_enable(pdev); + else + ret = port_disable(pdev); + mutex_unlock(&pdata->lock); + + return ret; +} + +static struct dfl_fpga_port_ops afu_port_ops = { + .name = DFL_FPGA_FEATURE_DEV_PORT, + .owner = THIS_MODULE, + .get_id = port_get_id, + .enable_set = port_enable_set, +}; + +static int afu_probe(struct platform_device *pdev) +{ + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + ret = afu_dev_init(pdev); + if (ret) + goto exit; + + ret = dfl_fpga_dev_feature_init(pdev, port_feature_drvs); + if (ret) + goto dev_destroy; + + ret = dfl_fpga_dev_ops_register(pdev, &afu_fops, THIS_MODULE); + if (ret) { + dfl_fpga_dev_feature_uinit(pdev); + goto dev_destroy; + } + + return 0; + +dev_destroy: + afu_dev_destroy(pdev); +exit: + return ret; +} + +static int afu_remove(struct platform_device *pdev) +{ + dev_dbg(&pdev->dev, "%s\n", __func__); + + dfl_fpga_dev_ops_unregister(pdev); + dfl_fpga_dev_feature_uinit(pdev); + afu_dev_destroy(pdev); + + return 0; +} + +static struct platform_driver afu_driver = { + .driver = { + .name = DFL_FPGA_FEATURE_DEV_PORT, + }, + .probe = afu_probe, + .remove = afu_remove, +}; + +static int __init afu_init(void) +{ + int ret; + + dfl_fpga_port_ops_add(&afu_port_ops); + + ret = platform_driver_register(&afu_driver); + if (ret) + dfl_fpga_port_ops_del(&afu_port_ops); + + return ret; +} + +static void __exit afu_exit(void) +{ + platform_driver_unregister(&afu_driver); + + dfl_fpga_port_ops_del(&afu_port_ops); +} + +module_init(afu_init); +module_exit(afu_exit); + +MODULE_DESCRIPTION("FPGA Accelerated Function Unit driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-port"); diff --git a/drivers/fpga/dfl-afu-region.c b/drivers/fpga/dfl-afu-region.c new file mode 100644 index 000000000000..0804b7a0c298 --- /dev/null +++ b/drivers/fpga/dfl-afu-region.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Accelerated Function Unit (AFU) MMIO Region Management + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ +#include "dfl-afu.h" + +/** + * afu_mmio_region_init - init function for afu mmio region support + * @pdata: afu platform device's pdata. + */ +void afu_mmio_region_init(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + + INIT_LIST_HEAD(&afu->regions); +} + +#define for_each_region(region, afu) \ + list_for_each_entry((region), &(afu)->regions, node) + +static struct dfl_afu_mmio_region *get_region_by_index(struct dfl_afu *afu, + u32 region_index) +{ + struct dfl_afu_mmio_region *region; + + for_each_region(region, afu) + if (region->index == region_index) + return region; + + return NULL; +} + +/** + * afu_mmio_region_add - add a mmio region to given feature dev. + * + * @region_index: region index. + * @region_size: region size. + * @phys: region's physical address of this region. + * @flags: region flags (access permission). + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_add(struct dfl_feature_platform_data *pdata, + u32 region_index, u64 region_size, u64 phys, u32 flags) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + region = devm_kzalloc(&pdata->dev->dev, sizeof(*region), GFP_KERNEL); + if (!region) + return -ENOMEM; + + region->index = region_index; + region->size = region_size; + region->phys = phys; + region->flags = flags; + + mutex_lock(&pdata->lock); + + afu = dfl_fpga_pdata_get_private(pdata); + + /* check if @index already exists */ + if (get_region_by_index(afu, region_index)) { + mutex_unlock(&pdata->lock); + ret = -EEXIST; + goto exit; + } + + region_size = PAGE_ALIGN(region_size); + region->offset = afu->region_cur_offset; + list_add(®ion->node, &afu->regions); + + afu->region_cur_offset += region_size; + afu->num_regions++; + mutex_unlock(&pdata->lock); + + return 0; + +exit: + devm_kfree(&pdata->dev->dev, region); + return ret; +} + +/** + * afu_mmio_region_destroy - destroy all mmio regions under given feature dev. + * @pdata: afu platform device's pdata. + */ +void afu_mmio_region_destroy(struct dfl_feature_platform_data *pdata) +{ + struct dfl_afu *afu = dfl_fpga_pdata_get_private(pdata); + struct dfl_afu_mmio_region *tmp, *region; + + list_for_each_entry_safe(region, tmp, &afu->regions, node) + devm_kfree(&pdata->dev->dev, region); +} + +/** + * afu_mmio_region_get_by_index - find an afu region by index. + * @pdata: afu platform device's pdata. + * @region_index: region index. + * @pregion: ptr to region for result. + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, + u32 region_index, + struct dfl_afu_mmio_region *pregion) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + region = get_region_by_index(afu, region_index); + if (!region) { + ret = -EINVAL; + goto exit; + } + *pregion = *region; +exit: + mutex_unlock(&pdata->lock); + return ret; +} + +/** + * afu_mmio_region_get_by_offset - find an afu mmio region by offset and size + * + * @pdata: afu platform device's pdata. + * @offset: region offset from start of the device fd. + * @size: region size. + * @pregion: ptr to region for result. + * + * Find the region which fully contains the region described by input + * parameters (offset and size) from the feature dev's region linked list. + * + * Return: 0 on success, negative error code otherwise. + */ +int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, + u64 offset, u64 size, + struct dfl_afu_mmio_region *pregion) +{ + struct dfl_afu_mmio_region *region; + struct dfl_afu *afu; + int ret = 0; + + mutex_lock(&pdata->lock); + afu = dfl_fpga_pdata_get_private(pdata); + for_each_region(region, afu) + if (region->offset <= offset && + region->offset + region->size >= offset + size) { + *pregion = *region; + goto exit; + } + ret = -EINVAL; +exit: + mutex_unlock(&pdata->lock); + return ret; +} diff --git a/drivers/fpga/dfl-afu.h b/drivers/fpga/dfl-afu.h new file mode 100644 index 000000000000..0c7630ae3cda --- /dev/null +++ b/drivers/fpga/dfl-afu.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Accelerated Function Unit (AFU) Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#ifndef __DFL_AFU_H +#define __DFL_AFU_H + +#include <linux/mm.h> + +#include "dfl.h" + +/** + * struct dfl_afu_mmio_region - afu mmio region data structure + * + * @index: region index. + * @flags: region flags (access permission). + * @size: region size. + * @offset: region offset from start of the device fd. + * @phys: region's physical address. + * @node: node to add to afu feature dev's region list. + */ +struct dfl_afu_mmio_region { + u32 index; + u32 flags; + u64 size; + u64 offset; + u64 phys; + struct list_head node; +}; + +/** + * struct fpga_afu_dma_region - afu DMA region data structure + * + * @user_addr: region userspace virtual address. + * @length: region length. + * @iova: region IO virtual address. + * @pages: ptr to pages of this region. + * @node: rb tree node. + * @in_use: flag to indicate if this region is in_use. + */ +struct dfl_afu_dma_region { + u64 user_addr; + u64 length; + u64 iova; + struct page **pages; + struct rb_node node; + bool in_use; +}; + +/** + * struct dfl_afu - afu device data structure + * + * @region_cur_offset: current region offset from start to the device fd. + * @num_regions: num of mmio regions. + * @regions: the mmio region linked list of this afu feature device. + * @dma_regions: root of dma regions rb tree. + * @num_umsgs: num of umsgs. + * @pdata: afu platform device's pdata. + */ +struct dfl_afu { + u64 region_cur_offset; + int num_regions; + u8 num_umsgs; + struct list_head regions; + struct rb_root dma_regions; + + struct dfl_feature_platform_data *pdata; +}; + +void afu_mmio_region_init(struct dfl_feature_platform_data *pdata); +int afu_mmio_region_add(struct dfl_feature_platform_data *pdata, + u32 region_index, u64 region_size, u64 phys, u32 flags); +void afu_mmio_region_destroy(struct dfl_feature_platform_data *pdata); +int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, + u32 region_index, + struct dfl_afu_mmio_region *pregion); +int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, + u64 offset, u64 size, + struct dfl_afu_mmio_region *pregion); +void afu_dma_region_init(struct dfl_feature_platform_data *pdata); +void afu_dma_region_destroy(struct dfl_feature_platform_data *pdata); +int afu_dma_map_region(struct dfl_feature_platform_data *pdata, + u64 user_addr, u64 length, u64 *iova); +int afu_dma_unmap_region(struct dfl_feature_platform_data *pdata, u64 iova); +struct dfl_afu_dma_region * +afu_dma_region_find(struct dfl_feature_platform_data *pdata, + u64 iova, u64 size); +#endif /* __DFL_AFU_H */ diff --git a/drivers/fpga/dfl-fme-br.c b/drivers/fpga/dfl-fme-br.c new file mode 100644 index 000000000000..7cc041def8b3 --- /dev/null +++ b/drivers/fpga/dfl-fme-br.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Bridge Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/module.h> +#include <linux/fpga/fpga-bridge.h> + +#include "dfl.h" +#include "dfl-fme-pr.h" + +struct fme_br_priv { + struct dfl_fme_br_pdata *pdata; + struct dfl_fpga_port_ops *port_ops; + struct platform_device *port_pdev; +}; + +static int fme_bridge_enable_set(struct fpga_bridge *bridge, bool enable) +{ + struct fme_br_priv *priv = bridge->priv; + struct platform_device *port_pdev; + struct dfl_fpga_port_ops *ops; + + if (!priv->port_pdev) { + port_pdev = dfl_fpga_cdev_find_port(priv->pdata->cdev, + &priv->pdata->port_id, + dfl_fpga_check_port_id); + if (!port_pdev) + return -ENODEV; + + priv->port_pdev = port_pdev; + } + + if (priv->port_pdev && !priv->port_ops) { + ops = dfl_fpga_port_ops_get(priv->port_pdev); + if (!ops || !ops->enable_set) + return -ENOENT; + + priv->port_ops = ops; + } + + return priv->port_ops->enable_set(priv->port_pdev, enable); +} + +static const struct fpga_bridge_ops fme_bridge_ops = { + .enable_set = fme_bridge_enable_set, +}; + +static int fme_br_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fme_br_priv *priv; + struct fpga_bridge *br; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->pdata = dev_get_platdata(dev); + + br = fpga_bridge_create(dev, "DFL FPGA FME Bridge", + &fme_bridge_ops, priv); + if (!br) + return -ENOMEM; + + platform_set_drvdata(pdev, br); + + ret = fpga_bridge_register(br); + if (ret) + fpga_bridge_free(br); + + return ret; +} + +static int fme_br_remove(struct platform_device *pdev) +{ + struct fpga_bridge *br = platform_get_drvdata(pdev); + struct fme_br_priv *priv = br->priv; + + fpga_bridge_unregister(br); + + if (priv->port_pdev) + put_device(&priv->port_pdev->dev); + if (priv->port_ops) + dfl_fpga_port_ops_put(priv->port_ops); + + return 0; +} + +static struct platform_driver fme_br_driver = { + .driver = { + .name = DFL_FPGA_FME_BRIDGE, + }, + .probe = fme_br_probe, + .remove = fme_br_remove, +}; + +module_platform_driver(fme_br_driver); + +MODULE_DESCRIPTION("FPGA Bridge for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-bridge"); diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c new file mode 100644 index 000000000000..086ad2420ade --- /dev/null +++ b/drivers/fpga/dfl-fme-main.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fpga-dfl.h> + +#include "dfl.h" +#include "dfl-fme.h" + +static ssize_t ports_num_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_CAP); + + return scnprintf(buf, PAGE_SIZE, "%u\n", + (unsigned int)FIELD_GET(FME_CAP_NUM_PORTS, v)); +} +static DEVICE_ATTR_RO(ports_num); + +/* + * Bitstream (static FPGA region) identifier number. It contains the + * detailed version and other information of this static FPGA region. + */ +static ssize_t bitstream_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_BITSTREAM_ID); + + return scnprintf(buf, PAGE_SIZE, "0x%llx\n", (unsigned long long)v); +} +static DEVICE_ATTR_RO(bitstream_id); + +/* + * Bitstream (static FPGA region) meta data. It contains the synthesis + * date, seed and other information of this static FPGA region. + */ +static ssize_t bitstream_metadata_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_BITSTREAM_MD); + + return scnprintf(buf, PAGE_SIZE, "0x%llx\n", (unsigned long long)v); +} +static DEVICE_ATTR_RO(bitstream_metadata); + +static const struct attribute *fme_hdr_attrs[] = { + &dev_attr_ports_num.attr, + &dev_attr_bitstream_id.attr, + &dev_attr_bitstream_metadata.attr, + NULL, +}; + +static int fme_hdr_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + void __iomem *base = feature->ioaddr; + int ret; + + dev_dbg(&pdev->dev, "FME HDR Init.\n"); + dev_dbg(&pdev->dev, "FME cap %llx.\n", + (unsigned long long)readq(base + FME_HDR_CAP)); + + ret = sysfs_create_files(&pdev->dev.kobj, fme_hdr_attrs); + if (ret) + return ret; + + return 0; +} + +static void fme_hdr_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + dev_dbg(&pdev->dev, "FME HDR UInit.\n"); + sysfs_remove_files(&pdev->dev.kobj, fme_hdr_attrs); +} + +static const struct dfl_feature_ops fme_hdr_ops = { + .init = fme_hdr_init, + .uinit = fme_hdr_uinit, +}; + +static struct dfl_feature_driver fme_feature_drvs[] = { + { + .id = FME_FEATURE_ID_HEADER, + .ops = &fme_hdr_ops, + }, + { + .id = FME_FEATURE_ID_PR_MGMT, + .ops = &pr_mgmt_ops, + }, + { + .ops = NULL, + }, +}; + +static long fme_ioctl_check_extension(struct dfl_feature_platform_data *pdata, + unsigned long arg) +{ + /* No extension support for now */ + return 0; +} + +static int fme_open(struct inode *inode, struct file *filp) +{ + struct platform_device *fdev = dfl_fpga_inode_to_feature_dev(inode); + struct dfl_feature_platform_data *pdata = dev_get_platdata(&fdev->dev); + int ret; + + if (WARN_ON(!pdata)) + return -ENODEV; + + ret = dfl_feature_dev_use_begin(pdata); + if (ret) + return ret; + + dev_dbg(&fdev->dev, "Device File Open\n"); + filp->private_data = pdata; + + return 0; +} + +static int fme_release(struct inode *inode, struct file *filp) +{ + struct dfl_feature_platform_data *pdata = filp->private_data; + struct platform_device *pdev = pdata->dev; + + dev_dbg(&pdev->dev, "Device File Release\n"); + dfl_feature_dev_use_end(pdata); + + return 0; +} + +static long fme_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct dfl_feature_platform_data *pdata = filp->private_data; + struct platform_device *pdev = pdata->dev; + struct dfl_feature *f; + long ret; + + dev_dbg(&pdev->dev, "%s cmd 0x%x\n", __func__, cmd); + + switch (cmd) { + case DFL_FPGA_GET_API_VERSION: + return DFL_FPGA_API_VERSION; + case DFL_FPGA_CHECK_EXTENSION: + return fme_ioctl_check_extension(pdata, arg); + default: + /* + * Let sub-feature's ioctl function to handle the cmd. + * Sub-feature's ioctl returns -ENODEV when cmd is not + * handled in this sub feature, and returns 0 or other + * error code if cmd is handled. + */ + dfl_fpga_dev_for_each_feature(pdata, f) { + if (f->ops && f->ops->ioctl) { + ret = f->ops->ioctl(pdev, f, cmd, arg); + if (ret != -ENODEV) + return ret; + } + } + } + + return -EINVAL; +} + +static int fme_dev_init(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *fme; + + fme = devm_kzalloc(&pdev->dev, sizeof(*fme), GFP_KERNEL); + if (!fme) + return -ENOMEM; + + fme->pdata = pdata; + + mutex_lock(&pdata->lock); + dfl_fpga_pdata_set_private(pdata, fme); + mutex_unlock(&pdata->lock); + + return 0; +} + +static void fme_dev_destroy(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *fme; + + mutex_lock(&pdata->lock); + fme = dfl_fpga_pdata_get_private(pdata); + dfl_fpga_pdata_set_private(pdata, NULL); + mutex_unlock(&pdata->lock); +} + +static const struct file_operations fme_fops = { + .owner = THIS_MODULE, + .open = fme_open, + .release = fme_release, + .unlocked_ioctl = fme_ioctl, +}; + +static int fme_probe(struct platform_device *pdev) +{ + int ret; + + ret = fme_dev_init(pdev); + if (ret) + goto exit; + + ret = dfl_fpga_dev_feature_init(pdev, fme_feature_drvs); + if (ret) + goto dev_destroy; + + ret = dfl_fpga_dev_ops_register(pdev, &fme_fops, THIS_MODULE); + if (ret) + goto feature_uinit; + + return 0; + +feature_uinit: + dfl_fpga_dev_feature_uinit(pdev); +dev_destroy: + fme_dev_destroy(pdev); +exit: + return ret; +} + +static int fme_remove(struct platform_device *pdev) +{ + dfl_fpga_dev_ops_unregister(pdev); + dfl_fpga_dev_feature_uinit(pdev); + fme_dev_destroy(pdev); + + return 0; +} + +static struct platform_driver fme_driver = { + .driver = { + .name = DFL_FPGA_FEATURE_DEV_FME, + }, + .probe = fme_probe, + .remove = fme_remove, +}; + +module_platform_driver(fme_driver); + +MODULE_DESCRIPTION("FPGA Management Engine driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme"); diff --git a/drivers/fpga/dfl-fme-mgr.c b/drivers/fpga/dfl-fme-mgr.c new file mode 100644 index 000000000000..b5ef405b6d88 --- /dev/null +++ b/drivers/fpga/dfl-fme-mgr.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Manager Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Christopher Rauer <christopher.rauer@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/bitfield.h> +#include <linux/module.h> +#include <linux/iopoll.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/fpga/fpga-mgr.h> + +#include "dfl-fme-pr.h" + +/* FME Partial Reconfiguration Sub Feature Register Set */ +#define FME_PR_DFH 0x0 +#define FME_PR_CTRL 0x8 +#define FME_PR_STS 0x10 +#define FME_PR_DATA 0x18 +#define FME_PR_ERR 0x20 +#define FME_PR_INTFC_ID_H 0xA8 +#define FME_PR_INTFC_ID_L 0xB0 + +/* FME PR Control Register Bitfield */ +#define FME_PR_CTRL_PR_RST BIT_ULL(0) /* Reset PR engine */ +#define FME_PR_CTRL_PR_RSTACK BIT_ULL(4) /* Ack for PR engine reset */ +#define FME_PR_CTRL_PR_RGN_ID GENMASK_ULL(9, 7) /* PR Region ID */ +#define FME_PR_CTRL_PR_START BIT_ULL(12) /* Start to request PR service */ +#define FME_PR_CTRL_PR_COMPLETE BIT_ULL(13) /* PR data push completion */ + +/* FME PR Status Register Bitfield */ +/* Number of available entries in HW queue inside the PR engine. */ +#define FME_PR_STS_PR_CREDIT GENMASK_ULL(8, 0) +#define FME_PR_STS_PR_STS BIT_ULL(16) /* PR operation status */ +#define FME_PR_STS_PR_STS_IDLE 0 +#define FME_PR_STS_PR_CTRLR_STS GENMASK_ULL(22, 20) /* Controller status */ +#define FME_PR_STS_PR_HOST_STS GENMASK_ULL(27, 24) /* PR host status */ + +/* FME PR Data Register Bitfield */ +/* PR data from the raw-binary file. */ +#define FME_PR_DATA_PR_DATA_RAW GENMASK_ULL(32, 0) + +/* FME PR Error Register */ +/* PR Operation errors detected. */ +#define FME_PR_ERR_OPERATION_ERR BIT_ULL(0) +/* CRC error detected. */ +#define FME_PR_ERR_CRC_ERR BIT_ULL(1) +/* Incompatible PR bitstream detected. */ +#define FME_PR_ERR_INCOMPATIBLE_BS BIT_ULL(2) +/* PR data push protocol violated. */ +#define FME_PR_ERR_PROTOCOL_ERR BIT_ULL(3) +/* PR data fifo overflow error detected */ +#define FME_PR_ERR_FIFO_OVERFLOW BIT_ULL(4) + +#define PR_WAIT_TIMEOUT 8000000 +#define PR_HOST_STATUS_IDLE 0 + +struct fme_mgr_priv { + void __iomem *ioaddr; + u64 pr_error; +}; + +static u64 pr_error_to_mgr_status(u64 err) +{ + u64 status = 0; + + if (err & FME_PR_ERR_OPERATION_ERR) + status |= FPGA_MGR_STATUS_OPERATION_ERR; + if (err & FME_PR_ERR_CRC_ERR) + status |= FPGA_MGR_STATUS_CRC_ERR; + if (err & FME_PR_ERR_INCOMPATIBLE_BS) + status |= FPGA_MGR_STATUS_INCOMPATIBLE_IMAGE_ERR; + if (err & FME_PR_ERR_PROTOCOL_ERR) + status |= FPGA_MGR_STATUS_IP_PROTOCOL_ERR; + if (err & FME_PR_ERR_FIFO_OVERFLOW) + status |= FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR; + + return status; +} + +static u64 fme_mgr_pr_error_handle(void __iomem *fme_pr) +{ + u64 pr_status, pr_error; + + pr_status = readq(fme_pr + FME_PR_STS); + if (!(pr_status & FME_PR_STS_PR_STS)) + return 0; + + pr_error = readq(fme_pr + FME_PR_ERR); + writeq(pr_error, fme_pr + FME_PR_ERR); + + return pr_error; +} + +static int fme_mgr_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl, pr_status; + + if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) { + dev_err(dev, "only supports partial reconfiguration.\n"); + return -EINVAL; + } + + dev_dbg(dev, "resetting PR before initiated PR\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_RST; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + if (readq_poll_timeout(fme_pr + FME_PR_CTRL, pr_ctrl, + pr_ctrl & FME_PR_CTRL_PR_RSTACK, 1, + PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Reset ACK timeout\n"); + return -ETIMEDOUT; + } + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl &= ~FME_PR_CTRL_PR_RST; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, + "waiting for PR resource in HW to be initialized and ready\n"); + + if (readq_poll_timeout(fme_pr + FME_PR_STS, pr_status, + (pr_status & FME_PR_STS_PR_STS) == + FME_PR_STS_PR_STS_IDLE, 1, PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Status timeout\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + return -ETIMEDOUT; + } + + dev_dbg(dev, "check and clear previous PR error\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + if (priv->pr_error) + dev_dbg(dev, "previous PR error detected %llx\n", + (unsigned long long)priv->pr_error); + + dev_dbg(dev, "set PR port ID\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl &= ~FME_PR_CTRL_PR_RGN_ID; + pr_ctrl |= FIELD_PREP(FME_PR_CTRL_PR_RGN_ID, info->region_id); + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + return 0; +} + +static int fme_mgr_write(struct fpga_manager *mgr, + const char *buf, size_t count) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl, pr_status, pr_data; + int delay = 0, pr_credit, i = 0; + + dev_dbg(dev, "start request\n"); + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_START; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, "pushing data from bitstream to HW\n"); + + /* + * driver can push data to PR hardware using PR_DATA register once HW + * has enough pr_credit (> 1), pr_credit reduces one for every 32bit + * pr data write to PR_DATA register. If pr_credit <= 1, driver needs + * to wait for enough pr_credit from hardware by polling. + */ + pr_status = readq(fme_pr + FME_PR_STS); + pr_credit = FIELD_GET(FME_PR_STS_PR_CREDIT, pr_status); + + while (count > 0) { + while (pr_credit <= 1) { + if (delay++ > PR_WAIT_TIMEOUT) { + dev_err(dev, "PR_CREDIT timeout\n"); + return -ETIMEDOUT; + } + udelay(1); + + pr_status = readq(fme_pr + FME_PR_STS); + pr_credit = FIELD_GET(FME_PR_STS_PR_CREDIT, pr_status); + } + + if (count < 4) { + dev_err(dev, "Invaild PR bitstream size\n"); + return -EINVAL; + } + + pr_data = 0; + pr_data |= FIELD_PREP(FME_PR_DATA_PR_DATA_RAW, + *(((u32 *)buf) + i)); + writeq(pr_data, fme_pr + FME_PR_DATA); + count -= 4; + pr_credit--; + i++; + } + + return 0; +} + +static int fme_mgr_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct device *dev = &mgr->dev; + struct fme_mgr_priv *priv = mgr->priv; + void __iomem *fme_pr = priv->ioaddr; + u64 pr_ctrl; + + pr_ctrl = readq(fme_pr + FME_PR_CTRL); + pr_ctrl |= FME_PR_CTRL_PR_COMPLETE; + writeq(pr_ctrl, fme_pr + FME_PR_CTRL); + + dev_dbg(dev, "green bitstream push complete\n"); + dev_dbg(dev, "waiting for HW to release PR resource\n"); + + if (readq_poll_timeout(fme_pr + FME_PR_CTRL, pr_ctrl, + !(pr_ctrl & FME_PR_CTRL_PR_START), 1, + PR_WAIT_TIMEOUT)) { + dev_err(dev, "PR Completion ACK timeout.\n"); + return -ETIMEDOUT; + } + + dev_dbg(dev, "PR operation complete, checking status\n"); + priv->pr_error = fme_mgr_pr_error_handle(fme_pr); + if (priv->pr_error) { + dev_dbg(dev, "PR error detected %llx\n", + (unsigned long long)priv->pr_error); + return -EIO; + } + + dev_dbg(dev, "PR done successfully\n"); + + return 0; +} + +static enum fpga_mgr_states fme_mgr_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static u64 fme_mgr_status(struct fpga_manager *mgr) +{ + struct fme_mgr_priv *priv = mgr->priv; + + return pr_error_to_mgr_status(priv->pr_error); +} + +static const struct fpga_manager_ops fme_mgr_ops = { + .write_init = fme_mgr_write_init, + .write = fme_mgr_write, + .write_complete = fme_mgr_write_complete, + .state = fme_mgr_state, + .status = fme_mgr_status, +}; + +static void fme_mgr_get_compat_id(void __iomem *fme_pr, + struct fpga_compat_id *id) +{ + id->id_l = readq(fme_pr + FME_PR_INTFC_ID_L); + id->id_h = readq(fme_pr + FME_PR_INTFC_ID_H); +} + +static int fme_mgr_probe(struct platform_device *pdev) +{ + struct dfl_fme_mgr_pdata *pdata = dev_get_platdata(&pdev->dev); + struct fpga_compat_id *compat_id; + struct device *dev = &pdev->dev; + struct fme_mgr_priv *priv; + struct fpga_manager *mgr; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (pdata->ioaddr) + priv->ioaddr = pdata->ioaddr; + + if (!priv->ioaddr) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->ioaddr = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ioaddr)) + return PTR_ERR(priv->ioaddr); + } + + compat_id = devm_kzalloc(dev, sizeof(*compat_id), GFP_KERNEL); + if (!compat_id) + return -ENOMEM; + + fme_mgr_get_compat_id(priv->ioaddr, compat_id); + + mgr = fpga_mgr_create(dev, "DFL FME FPGA Manager", + &fme_mgr_ops, priv); + if (!mgr) + return -ENOMEM; + + mgr->compat_id = compat_id; + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) + fpga_mgr_free(mgr); + + return ret; +} + +static int fme_mgr_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + + fpga_mgr_unregister(mgr); + + return 0; +} + +static struct platform_driver fme_mgr_driver = { + .driver = { + .name = DFL_FPGA_FME_MGR, + }, + .probe = fme_mgr_probe, + .remove = fme_mgr_remove, +}; + +module_platform_driver(fme_mgr_driver); + +MODULE_DESCRIPTION("FPGA Manager for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-mgr"); diff --git a/drivers/fpga/dfl-fme-pr.c b/drivers/fpga/dfl-fme-pr.c new file mode 100644 index 000000000000..fc9fd2d0482f --- /dev/null +++ b/drivers/fpga/dfl-fme-pr.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Management Engine (FME) Partial Reconfiguration + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Christopher Rauer <christopher.rauer@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/vmalloc.h> +#include <linux/uaccess.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/fpga/fpga-bridge.h> +#include <linux/fpga/fpga-region.h> +#include <linux/fpga-dfl.h> + +#include "dfl.h" +#include "dfl-fme.h" +#include "dfl-fme-pr.h" + +static struct dfl_fme_region * +dfl_fme_region_find_by_port_id(struct dfl_fme *fme, int port_id) +{ + struct dfl_fme_region *fme_region; + + list_for_each_entry(fme_region, &fme->region_list, node) + if (fme_region->port_id == port_id) + return fme_region; + + return NULL; +} + +static int dfl_fme_region_match(struct device *dev, const void *data) +{ + return dev->parent == data; +} + +static struct fpga_region *dfl_fme_region_find(struct dfl_fme *fme, int port_id) +{ + struct dfl_fme_region *fme_region; + struct fpga_region *region; + + fme_region = dfl_fme_region_find_by_port_id(fme, port_id); + if (!fme_region) + return NULL; + + region = fpga_region_class_find(NULL, &fme_region->region->dev, + dfl_fme_region_match); + if (!region) + return NULL; + + return region; +} + +static int fme_pr(struct platform_device *pdev, unsigned long arg) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + void __user *argp = (void __user *)arg; + struct dfl_fpga_fme_port_pr port_pr; + struct fpga_image_info *info; + struct fpga_region *region; + void __iomem *fme_hdr; + struct dfl_fme *fme; + unsigned long minsz; + void *buf = NULL; + int ret = 0; + u64 v; + + minsz = offsetofend(struct dfl_fpga_fme_port_pr, buffer_address); + + if (copy_from_user(&port_pr, argp, minsz)) + return -EFAULT; + + if (port_pr.argsz < minsz || port_pr.flags) + return -EINVAL; + + if (!IS_ALIGNED(port_pr.buffer_size, 4)) + return -EINVAL; + + /* get fme header region */ + fme_hdr = dfl_get_feature_ioaddr_by_id(&pdev->dev, + FME_FEATURE_ID_HEADER); + + /* check port id */ + v = readq(fme_hdr + FME_HDR_CAP); + if (port_pr.port_id >= FIELD_GET(FME_CAP_NUM_PORTS, v)) { + dev_dbg(&pdev->dev, "port number more than maximum\n"); + return -EINVAL; + } + + if (!access_ok(VERIFY_READ, + (void __user *)(unsigned long)port_pr.buffer_address, + port_pr.buffer_size)) + return -EFAULT; + + buf = vmalloc(port_pr.buffer_size); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, + (void __user *)(unsigned long)port_pr.buffer_address, + port_pr.buffer_size)) { + ret = -EFAULT; + goto free_exit; + } + + /* prepare fpga_image_info for PR */ + info = fpga_image_info_alloc(&pdev->dev); + if (!info) { + ret = -ENOMEM; + goto free_exit; + } + + info->flags |= FPGA_MGR_PARTIAL_RECONFIG; + + mutex_lock(&pdata->lock); + fme = dfl_fpga_pdata_get_private(pdata); + /* fme device has been unregistered. */ + if (!fme) { + ret = -EINVAL; + goto unlock_exit; + } + + region = dfl_fme_region_find(fme, port_pr.port_id); + if (!region) { + ret = -EINVAL; + goto unlock_exit; + } + + fpga_image_info_free(region->info); + + info->buf = buf; + info->count = port_pr.buffer_size; + info->region_id = port_pr.port_id; + region->info = info; + + ret = fpga_region_program_fpga(region); + + /* + * it allows userspace to reset the PR region's logic by disabling and + * reenabling the bridge to clear things out between accleration runs. + * so no need to hold the bridges after partial reconfiguration. + */ + if (region->get_bridges) + fpga_bridges_put(®ion->bridge_list); + + put_device(®ion->dev); +unlock_exit: + mutex_unlock(&pdata->lock); +free_exit: + vfree(buf); + if (copy_to_user((void __user *)arg, &port_pr, minsz)) + return -EFAULT; + + return ret; +} + +/** + * dfl_fme_create_mgr - create fpga mgr platform device as child device + * + * @pdata: fme platform_device's pdata + * + * Return: mgr platform device if successful, and error code otherwise. + */ +static struct platform_device * +dfl_fme_create_mgr(struct dfl_feature_platform_data *pdata, + struct dfl_feature *feature) +{ + struct platform_device *mgr, *fme = pdata->dev; + struct dfl_fme_mgr_pdata mgr_pdata; + int ret = -ENOMEM; + + if (!feature->ioaddr) + return ERR_PTR(-ENODEV); + + mgr_pdata.ioaddr = feature->ioaddr; + + /* + * Each FME has only one fpga-mgr, so allocate platform device using + * the same FME platform device id. + */ + mgr = platform_device_alloc(DFL_FPGA_FME_MGR, fme->id); + if (!mgr) + return ERR_PTR(ret); + + mgr->dev.parent = &fme->dev; + + ret = platform_device_add_data(mgr, &mgr_pdata, sizeof(mgr_pdata)); + if (ret) + goto create_mgr_err; + + ret = platform_device_add(mgr); + if (ret) + goto create_mgr_err; + + return mgr; + +create_mgr_err: + platform_device_put(mgr); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_mgr - destroy fpga mgr platform device + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_mgr(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + + platform_device_unregister(priv->mgr); +} + +/** + * dfl_fme_create_bridge - create fme fpga bridge platform device as child + * + * @pdata: fme platform device's pdata + * @port_id: port id for the bridge to be created. + * + * Return: bridge platform device if successful, and error code otherwise. + */ +static struct dfl_fme_bridge * +dfl_fme_create_bridge(struct dfl_feature_platform_data *pdata, int port_id) +{ + struct device *dev = &pdata->dev->dev; + struct dfl_fme_br_pdata br_pdata; + struct dfl_fme_bridge *fme_br; + int ret = -ENOMEM; + + fme_br = devm_kzalloc(dev, sizeof(*fme_br), GFP_KERNEL); + if (!fme_br) + return ERR_PTR(ret); + + br_pdata.cdev = pdata->dfl_cdev; + br_pdata.port_id = port_id; + + fme_br->br = platform_device_alloc(DFL_FPGA_FME_BRIDGE, + PLATFORM_DEVID_AUTO); + if (!fme_br->br) + return ERR_PTR(ret); + + fme_br->br->dev.parent = dev; + + ret = platform_device_add_data(fme_br->br, &br_pdata, sizeof(br_pdata)); + if (ret) + goto create_br_err; + + ret = platform_device_add(fme_br->br); + if (ret) + goto create_br_err; + + return fme_br; + +create_br_err: + platform_device_put(fme_br->br); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_bridge - destroy fpga bridge platform device + * @fme_br: fme bridge to destroy + */ +static void dfl_fme_destroy_bridge(struct dfl_fme_bridge *fme_br) +{ + platform_device_unregister(fme_br->br); +} + +/** + * dfl_fme_destroy_bridge - destroy all fpga bridge platform device + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_bridges(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + struct dfl_fme_bridge *fbridge, *tmp; + + list_for_each_entry_safe(fbridge, tmp, &priv->bridge_list, node) { + list_del(&fbridge->node); + dfl_fme_destroy_bridge(fbridge); + } +} + +/** + * dfl_fme_create_region - create fpga region platform device as child + * + * @pdata: fme platform device's pdata + * @mgr: mgr platform device needed for region + * @br: br platform device needed for region + * @port_id: port id + * + * Return: fme region if successful, and error code otherwise. + */ +static struct dfl_fme_region * +dfl_fme_create_region(struct dfl_feature_platform_data *pdata, + struct platform_device *mgr, + struct platform_device *br, int port_id) +{ + struct dfl_fme_region_pdata region_pdata; + struct device *dev = &pdata->dev->dev; + struct dfl_fme_region *fme_region; + int ret = -ENOMEM; + + fme_region = devm_kzalloc(dev, sizeof(*fme_region), GFP_KERNEL); + if (!fme_region) + return ERR_PTR(ret); + + region_pdata.mgr = mgr; + region_pdata.br = br; + + /* + * Each FPGA device may have more than one port, so allocate platform + * device using the same port platform device id. + */ + fme_region->region = platform_device_alloc(DFL_FPGA_FME_REGION, br->id); + if (!fme_region->region) + return ERR_PTR(ret); + + fme_region->region->dev.parent = dev; + + ret = platform_device_add_data(fme_region->region, ®ion_pdata, + sizeof(region_pdata)); + if (ret) + goto create_region_err; + + ret = platform_device_add(fme_region->region); + if (ret) + goto create_region_err; + + fme_region->port_id = port_id; + + return fme_region; + +create_region_err: + platform_device_put(fme_region->region); + return ERR_PTR(ret); +} + +/** + * dfl_fme_destroy_region - destroy fme region + * @fme_region: fme region to destroy + */ +static void dfl_fme_destroy_region(struct dfl_fme_region *fme_region) +{ + platform_device_unregister(fme_region->region); +} + +/** + * dfl_fme_destroy_regions - destroy all fme regions + * @pdata: fme platform device's pdata + */ +static void dfl_fme_destroy_regions(struct dfl_feature_platform_data *pdata) +{ + struct dfl_fme *priv = dfl_fpga_pdata_get_private(pdata); + struct dfl_fme_region *fme_region, *tmp; + + list_for_each_entry_safe(fme_region, tmp, &priv->region_list, node) { + list_del(&fme_region->node); + dfl_fme_destroy_region(fme_region); + } +} + +static int pr_mgmt_init(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme_region *fme_region; + struct dfl_fme_bridge *fme_br; + struct platform_device *mgr; + struct dfl_fme *priv; + void __iomem *fme_hdr; + int ret = -ENODEV, i = 0; + u64 fme_cap, port_offset; + + fme_hdr = dfl_get_feature_ioaddr_by_id(&pdev->dev, + FME_FEATURE_ID_HEADER); + + mutex_lock(&pdata->lock); + priv = dfl_fpga_pdata_get_private(pdata); + + /* Initialize the region and bridge sub device list */ + INIT_LIST_HEAD(&priv->region_list); + INIT_LIST_HEAD(&priv->bridge_list); + + /* Create fpga mgr platform device */ + mgr = dfl_fme_create_mgr(pdata, feature); + if (IS_ERR(mgr)) { + dev_err(&pdev->dev, "fail to create fpga mgr pdev\n"); + goto unlock; + } + + priv->mgr = mgr; + + /* Read capability register to check number of regions and bridges */ + fme_cap = readq(fme_hdr + FME_HDR_CAP); + for (; i < FIELD_GET(FME_CAP_NUM_PORTS, fme_cap); i++) { + port_offset = readq(fme_hdr + FME_HDR_PORT_OFST(i)); + if (!(port_offset & FME_PORT_OFST_IMP)) + continue; + + /* Create bridge for each port */ + fme_br = dfl_fme_create_bridge(pdata, i); + if (IS_ERR(fme_br)) { + ret = PTR_ERR(fme_br); + goto destroy_region; + } + + list_add(&fme_br->node, &priv->bridge_list); + + /* Create region for each port */ + fme_region = dfl_fme_create_region(pdata, mgr, + fme_br->br, i); + if (!fme_region) { + ret = PTR_ERR(fme_region); + goto destroy_region; + } + + list_add(&fme_region->node, &priv->region_list); + } + mutex_unlock(&pdata->lock); + + return 0; + +destroy_region: + dfl_fme_destroy_regions(pdata); + dfl_fme_destroy_bridges(pdata); + dfl_fme_destroy_mgr(pdata); +unlock: + mutex_unlock(&pdata->lock); + return ret; +} + +static void pr_mgmt_uinit(struct platform_device *pdev, + struct dfl_feature *feature) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fme *priv; + + mutex_lock(&pdata->lock); + priv = dfl_fpga_pdata_get_private(pdata); + + dfl_fme_destroy_regions(pdata); + dfl_fme_destroy_bridges(pdata); + dfl_fme_destroy_mgr(pdata); + mutex_unlock(&pdata->lock); +} + +static long fme_pr_ioctl(struct platform_device *pdev, + struct dfl_feature *feature, + unsigned int cmd, unsigned long arg) +{ + long ret; + + switch (cmd) { + case DFL_FPGA_FME_PORT_PR: + ret = fme_pr(pdev, arg); + break; + default: + ret = -ENODEV; + } + + return ret; +} + +const struct dfl_feature_ops pr_mgmt_ops = { + .init = pr_mgmt_init, + .uinit = pr_mgmt_uinit, + .ioctl = fme_pr_ioctl, +}; diff --git a/drivers/fpga/dfl-fme-pr.h b/drivers/fpga/dfl-fme-pr.h new file mode 100644 index 000000000000..096a699089d3 --- /dev/null +++ b/drivers/fpga/dfl-fme-pr.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Management Engine (FME) Partial Reconfiguration Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#ifndef __DFL_FME_PR_H +#define __DFL_FME_PR_H + +#include <linux/platform_device.h> + +/** + * struct dfl_fme_region - FME fpga region data structure + * + * @region: platform device of the FPGA region. + * @node: used to link fme_region to a list. + * @port_id: indicate which port this region connected to. + */ +struct dfl_fme_region { + struct platform_device *region; + struct list_head node; + int port_id; +}; + +/** + * struct dfl_fme_region_pdata - platform data for FME region platform device. + * + * @mgr: platform device of the FPGA manager. + * @br: platform device of the FPGA bridge. + * @region_id: region id (same as port_id). + */ +struct dfl_fme_region_pdata { + struct platform_device *mgr; + struct platform_device *br; + int region_id; +}; + +/** + * struct dfl_fme_bridge - FME fpga bridge data structure + * + * @br: platform device of the FPGA bridge. + * @node: used to link fme_bridge to a list. + */ +struct dfl_fme_bridge { + struct platform_device *br; + struct list_head node; +}; + +/** + * struct dfl_fme_bridge_pdata - platform data for FME bridge platform device. + * + * @cdev: container device. + * @port_id: port id. + */ +struct dfl_fme_br_pdata { + struct dfl_fpga_cdev *cdev; + int port_id; +}; + +/** + * struct dfl_fme_mgr_pdata - platform data for FME manager platform device. + * + * @ioaddr: mapped io address for FME manager platform device. + */ +struct dfl_fme_mgr_pdata { + void __iomem *ioaddr; +}; + +#define DFL_FPGA_FME_MGR "dfl-fme-mgr" +#define DFL_FPGA_FME_BRIDGE "dfl-fme-bridge" +#define DFL_FPGA_FME_REGION "dfl-fme-region" + +#endif /* __DFL_FME_PR_H */ diff --git a/drivers/fpga/dfl-fme-region.c b/drivers/fpga/dfl-fme-region.c new file mode 100644 index 000000000000..0b7e19c27c6d --- /dev/null +++ b/drivers/fpga/dfl-fme-region.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FPGA Region Driver for FPGA Management Engine (FME) + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/module.h> +#include <linux/fpga/fpga-region.h> + +#include "dfl-fme-pr.h" + +static int fme_region_get_bridges(struct fpga_region *region) +{ + struct dfl_fme_region_pdata *pdata = region->priv; + struct device *dev = &pdata->br->dev; + + return fpga_bridge_get_to_list(dev, region->info, ®ion->bridge_list); +} + +static int fme_region_probe(struct platform_device *pdev) +{ + struct dfl_fme_region_pdata *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct fpga_region *region; + struct fpga_manager *mgr; + int ret; + + mgr = fpga_mgr_get(&pdata->mgr->dev); + if (IS_ERR(mgr)) + return -EPROBE_DEFER; + + region = fpga_region_create(dev, mgr, fme_region_get_bridges); + if (!region) { + ret = -ENOMEM; + goto eprobe_mgr_put; + } + + region->priv = pdata; + region->compat_id = mgr->compat_id; + platform_set_drvdata(pdev, region); + + ret = fpga_region_register(region); + if (ret) + goto region_free; + + dev_dbg(dev, "DFL FME FPGA Region probed\n"); + + return 0; + +region_free: + fpga_region_free(region); +eprobe_mgr_put: + fpga_mgr_put(mgr); + return ret; +} + +static int fme_region_remove(struct platform_device *pdev) +{ + struct fpga_region *region = dev_get_drvdata(&pdev->dev); + + fpga_region_unregister(region); + fpga_mgr_put(region->mgr); + + return 0; +} + +static struct platform_driver fme_region_driver = { + .driver = { + .name = DFL_FPGA_FME_REGION, + }, + .probe = fme_region_probe, + .remove = fme_region_remove, +}; + +module_platform_driver(fme_region_driver); + +MODULE_DESCRIPTION("FPGA Region for DFL FPGA Management Engine"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:dfl-fme-region"); diff --git a/drivers/fpga/dfl-fme.h b/drivers/fpga/dfl-fme.h new file mode 100644 index 000000000000..5394a216c5c0 --- /dev/null +++ b/drivers/fpga/dfl-fme.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Header file for FPGA Management Engine (FME) Driver + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Wu Hao <hao.wu@intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#ifndef __DFL_FME_H +#define __DFL_FME_H + +/** + * struct dfl_fme - dfl fme private data + * + * @mgr: FME's FPGA manager platform device. + * @region_list: linked list of FME's FPGA regions. + * @bridge_list: linked list of FME's FPGA bridges. + * @pdata: fme platform device's pdata. + */ +struct dfl_fme { + struct platform_device *mgr; + struct list_head region_list; + struct list_head bridge_list; + struct dfl_feature_platform_data *pdata; +}; + +extern const struct dfl_feature_ops pr_mgmt_ops; + +#endif /* __DFL_FME_H */ diff --git a/drivers/fpga/dfl-pci.c b/drivers/fpga/dfl-pci.c new file mode 100644 index 000000000000..66b5720582bb --- /dev/null +++ b/drivers/fpga/dfl-pci.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Device Feature List (DFL) PCIe device + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Zhang Yi <Yi.Z.Zhang@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + * Joseph Grecco <joe.grecco@intel.com> + * Enno Luebbers <enno.luebbers@intel.com> + * Tim Whisonant <tim.whisonant@intel.com> + * Ananda Ravuri <ananda.ravuri@intel.com> + * Henry Mitchel <henry.mitchel@intel.com> + */ + +#include <linux/pci.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/stddef.h> +#include <linux/errno.h> +#include <linux/aer.h> + +#include "dfl.h" + +#define DRV_VERSION "0.8" +#define DRV_NAME "dfl-pci" + +struct cci_drvdata { + struct dfl_fpga_cdev *cdev; /* container device */ +}; + +static void __iomem *cci_pci_ioremap_bar(struct pci_dev *pcidev, int bar) +{ + if (pcim_iomap_regions(pcidev, BIT(bar), DRV_NAME)) + return NULL; + + return pcim_iomap_table(pcidev)[bar]; +} + +/* PCI Device ID */ +#define PCIE_DEVICE_ID_PF_INT_5_X 0xBCBD +#define PCIE_DEVICE_ID_PF_INT_6_X 0xBCC0 +#define PCIE_DEVICE_ID_PF_DSC_1_X 0x09C4 +/* VF Device */ +#define PCIE_DEVICE_ID_VF_INT_5_X 0xBCBF +#define PCIE_DEVICE_ID_VF_INT_6_X 0xBCC1 +#define PCIE_DEVICE_ID_VF_DSC_1_X 0x09C5 + +static struct pci_device_id cci_pcie_id_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_INT_5_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_INT_5_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_INT_6_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_INT_6_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_PF_DSC_1_X),}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCIE_DEVICE_ID_VF_DSC_1_X),}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, cci_pcie_id_tbl); + +static int cci_init_drvdata(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata; + + drvdata = devm_kzalloc(&pcidev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + pci_set_drvdata(pcidev, drvdata); + + return 0; +} + +static void cci_remove_feature_devs(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata = pci_get_drvdata(pcidev); + + /* remove all children feature devices */ + dfl_fpga_feature_devs_remove(drvdata->cdev); +} + +/* enumerate feature devices under pci device */ +static int cci_enumerate_feature_devs(struct pci_dev *pcidev) +{ + struct cci_drvdata *drvdata = pci_get_drvdata(pcidev); + struct dfl_fpga_enum_info *info; + struct dfl_fpga_cdev *cdev; + resource_size_t start, len; + int port_num, bar, i, ret = 0; + void __iomem *base; + u32 offset; + u64 v; + + /* allocate enumeration info via pci_dev */ + info = dfl_fpga_enum_info_alloc(&pcidev->dev); + if (!info) + return -ENOMEM; + + /* start to find Device Feature List from Bar 0 */ + base = cci_pci_ioremap_bar(pcidev, 0); + if (!base) { + ret = -ENOMEM; + goto enum_info_free_exit; + } + + /* + * PF device has FME and Ports/AFUs, and VF device only has one + * Port/AFU. Check them and add related "Device Feature List" info + * for the next step enumeration. + */ + if (dfl_feature_is_fme(base)) { + start = pci_resource_start(pcidev, 0); + len = pci_resource_len(pcidev, 0); + + dfl_fpga_enum_info_add_dfl(info, start, len, base); + + /* + * find more Device Feature Lists (e.g. Ports) per information + * indicated by FME module. + */ + v = readq(base + FME_HDR_CAP); + port_num = FIELD_GET(FME_CAP_NUM_PORTS, v); + + WARN_ON(port_num > MAX_DFL_FPGA_PORT_NUM); + + for (i = 0; i < port_num; i++) { + v = readq(base + FME_HDR_PORT_OFST(i)); + + /* skip ports which are not implemented. */ + if (!(v & FME_PORT_OFST_IMP)) + continue; + + /* + * add Port's Device Feature List information for next + * step enumeration. + */ + bar = FIELD_GET(FME_PORT_OFST_BAR_ID, v); + offset = FIELD_GET(FME_PORT_OFST_DFH_OFST, v); + base = cci_pci_ioremap_bar(pcidev, bar); + if (!base) + continue; + + start = pci_resource_start(pcidev, bar) + offset; + len = pci_resource_len(pcidev, bar) - offset; + + dfl_fpga_enum_info_add_dfl(info, start, len, + base + offset); + } + } else if (dfl_feature_is_port(base)) { + start = pci_resource_start(pcidev, 0); + len = pci_resource_len(pcidev, 0); + + dfl_fpga_enum_info_add_dfl(info, start, len, base); + } else { + ret = -ENODEV; + goto enum_info_free_exit; + } + + /* start enumeration with prepared enumeration information */ + cdev = dfl_fpga_feature_devs_enumerate(info); + if (IS_ERR(cdev)) { + dev_err(&pcidev->dev, "Enumeration failure\n"); + ret = PTR_ERR(cdev); + goto enum_info_free_exit; + } + + drvdata->cdev = cdev; + +enum_info_free_exit: + dfl_fpga_enum_info_free(info); + + return ret; +} + +static +int cci_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *pcidevid) +{ + int ret; + + ret = pcim_enable_device(pcidev); + if (ret < 0) { + dev_err(&pcidev->dev, "Failed to enable device %d.\n", ret); + return ret; + } + + ret = pci_enable_pcie_error_reporting(pcidev); + if (ret && ret != -EINVAL) + dev_info(&pcidev->dev, "PCIE AER unavailable %d.\n", ret); + + pci_set_master(pcidev); + + if (!pci_set_dma_mask(pcidev, DMA_BIT_MASK(64))) { + ret = pci_set_consistent_dma_mask(pcidev, DMA_BIT_MASK(64)); + if (ret) + goto disable_error_report_exit; + } else if (!pci_set_dma_mask(pcidev, DMA_BIT_MASK(32))) { + ret = pci_set_consistent_dma_mask(pcidev, DMA_BIT_MASK(32)); + if (ret) + goto disable_error_report_exit; + } else { + ret = -EIO; + dev_err(&pcidev->dev, "No suitable DMA support available.\n"); + goto disable_error_report_exit; + } + + ret = cci_init_drvdata(pcidev); + if (ret) { + dev_err(&pcidev->dev, "Fail to init drvdata %d.\n", ret); + goto disable_error_report_exit; + } + + ret = cci_enumerate_feature_devs(pcidev); + if (ret) { + dev_err(&pcidev->dev, "enumeration failure %d.\n", ret); + goto disable_error_report_exit; + } + + return ret; + +disable_error_report_exit: + pci_disable_pcie_error_reporting(pcidev); + return ret; +} + +static void cci_pci_remove(struct pci_dev *pcidev) +{ + cci_remove_feature_devs(pcidev); + pci_disable_pcie_error_reporting(pcidev); +} + +static struct pci_driver cci_pci_driver = { + .name = DRV_NAME, + .id_table = cci_pcie_id_tbl, + .probe = cci_pci_probe, + .remove = cci_pci_remove, +}; + +module_pci_driver(cci_pci_driver); + +MODULE_DESCRIPTION("FPGA DFL PCIe Device Driver"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c new file mode 100644 index 000000000000..a9b521bccb06 --- /dev/null +++ b/drivers/fpga/dfl.c @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for FPGA Device Feature List (DFL) Support + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Zhang Yi <yi.z.zhang@intel.com> + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ +#include <linux/module.h> + +#include "dfl.h" + +static DEFINE_MUTEX(dfl_id_mutex); + +/* + * when adding a new feature dev support in DFL framework, it's required to + * add a new item in enum dfl_id_type and provide related information in below + * dfl_devs table which is indexed by dfl_id_type, e.g. name string used for + * platform device creation (define name strings in dfl.h, as they could be + * reused by platform device drivers). + * + * if the new feature dev needs chardev support, then it's required to add + * a new item in dfl_chardevs table and configure dfl_devs[i].devt_type as + * index to dfl_chardevs table. If no chardev support just set devt_type + * as one invalid index (DFL_FPGA_DEVT_MAX). + */ +enum dfl_id_type { + FME_ID, /* fme id allocation and mapping */ + PORT_ID, /* port id allocation and mapping */ + DFL_ID_MAX, +}; + +enum dfl_fpga_devt_type { + DFL_FPGA_DEVT_FME, + DFL_FPGA_DEVT_PORT, + DFL_FPGA_DEVT_MAX, +}; + +/** + * dfl_dev_info - dfl feature device information. + * @name: name string of the feature platform device. + * @dfh_id: id value in Device Feature Header (DFH) register by DFL spec. + * @id: idr id of the feature dev. + * @devt_type: index to dfl_chrdevs[]. + */ +struct dfl_dev_info { + const char *name; + u32 dfh_id; + struct idr id; + enum dfl_fpga_devt_type devt_type; +}; + +/* it is indexed by dfl_id_type */ +static struct dfl_dev_info dfl_devs[] = { + {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME, + .devt_type = DFL_FPGA_DEVT_FME}, + {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT, + .devt_type = DFL_FPGA_DEVT_PORT}, +}; + +/** + * dfl_chardev_info - chardev information of dfl feature device + * @name: nmae string of the char device. + * @devt: devt of the char device. + */ +struct dfl_chardev_info { + const char *name; + dev_t devt; +}; + +/* indexed by enum dfl_fpga_devt_type */ +static struct dfl_chardev_info dfl_chrdevs[] = { + {.name = DFL_FPGA_FEATURE_DEV_FME}, + {.name = DFL_FPGA_FEATURE_DEV_PORT}, +}; + +static void dfl_ids_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + idr_init(&dfl_devs[i].id); +} + +static void dfl_ids_destroy(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + idr_destroy(&dfl_devs[i].id); +} + +static int dfl_id_alloc(enum dfl_id_type type, struct device *dev) +{ + int id; + + WARN_ON(type >= DFL_ID_MAX); + mutex_lock(&dfl_id_mutex); + id = idr_alloc(&dfl_devs[type].id, dev, 0, 0, GFP_KERNEL); + mutex_unlock(&dfl_id_mutex); + + return id; +} + +static void dfl_id_free(enum dfl_id_type type, int id) +{ + WARN_ON(type >= DFL_ID_MAX); + mutex_lock(&dfl_id_mutex); + idr_remove(&dfl_devs[type].id, id); + mutex_unlock(&dfl_id_mutex); +} + +static enum dfl_id_type feature_dev_id_type(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + if (!strcmp(dfl_devs[i].name, pdev->name)) + return i; + + return DFL_ID_MAX; +} + +static enum dfl_id_type dfh_id_to_type(u32 id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) + if (dfl_devs[i].dfh_id == id) + return i; + + return DFL_ID_MAX; +} + +/* + * introduce a global port_ops list, it allows port drivers to register ops + * in such list, then other feature devices (e.g. FME), could use the port + * functions even related port platform device is hidden. Below is one example, + * in virtualization case of PCIe-based FPGA DFL device, when SRIOV is + * enabled, port (and it's AFU) is turned into VF and port platform device + * is hidden from system but it's still required to access port to finish FPGA + * reconfiguration function in FME. + */ + +static DEFINE_MUTEX(dfl_port_ops_mutex); +static LIST_HEAD(dfl_port_ops_list); + +/** + * dfl_fpga_port_ops_get - get matched port ops from the global list + * @pdev: platform device to match with associated port ops. + * Return: matched port ops on success, NULL otherwise. + * + * Please note that must dfl_fpga_port_ops_put after use the port_ops. + */ +struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev) +{ + struct dfl_fpga_port_ops *ops = NULL; + + mutex_lock(&dfl_port_ops_mutex); + if (list_empty(&dfl_port_ops_list)) + goto done; + + list_for_each_entry(ops, &dfl_port_ops_list, node) { + /* match port_ops using the name of platform device */ + if (!strcmp(pdev->name, ops->name)) { + if (!try_module_get(ops->owner)) + ops = NULL; + goto done; + } + } + + ops = NULL; +done: + mutex_unlock(&dfl_port_ops_mutex); + return ops; +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_get); + +/** + * dfl_fpga_port_ops_put - put port ops + * @ops: port ops. + */ +void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops) +{ + if (ops && ops->owner) + module_put(ops->owner); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_put); + +/** + * dfl_fpga_port_ops_add - add port_ops to global list + * @ops: port ops to add. + */ +void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops) +{ + mutex_lock(&dfl_port_ops_mutex); + list_add_tail(&ops->node, &dfl_port_ops_list); + mutex_unlock(&dfl_port_ops_mutex); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_add); + +/** + * dfl_fpga_port_ops_del - remove port_ops from global list + * @ops: port ops to del. + */ +void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops) +{ + mutex_lock(&dfl_port_ops_mutex); + list_del(&ops->node); + mutex_unlock(&dfl_port_ops_mutex); +} +EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_del); + +/** + * dfl_fpga_check_port_id - check the port id + * @pdev: port platform device. + * @pport_id: port id to compare. + * + * Return: 1 if port device matches with given port id, otherwise 0. + */ +int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id) +{ + struct dfl_fpga_port_ops *port_ops = dfl_fpga_port_ops_get(pdev); + int port_id; + + if (!port_ops || !port_ops->get_id) + return 0; + + port_id = port_ops->get_id(pdev); + dfl_fpga_port_ops_put(port_ops); + + return port_id == *(int *)pport_id; +} +EXPORT_SYMBOL_GPL(dfl_fpga_check_port_id); + +/** + * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device + * @pdev: feature device. + */ +void dfl_fpga_dev_feature_uinit(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_feature *feature; + + dfl_fpga_dev_for_each_feature(pdata, feature) + if (feature->ops) { + feature->ops->uinit(pdev, feature); + feature->ops = NULL; + } +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_uinit); + +static int dfl_feature_instance_init(struct platform_device *pdev, + struct dfl_feature_platform_data *pdata, + struct dfl_feature *feature, + struct dfl_feature_driver *drv) +{ + int ret; + + ret = drv->ops->init(pdev, feature); + if (ret) + return ret; + + feature->ops = drv->ops; + + return ret; +} + +/** + * dfl_fpga_dev_feature_init - init for sub features of dfl feature device + * @pdev: feature device. + * @feature_drvs: drvs for sub features. + * + * This function will match sub features with given feature drvs list and + * use matched drv to init related sub feature. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_dev_feature_init(struct platform_device *pdev, + struct dfl_feature_driver *feature_drvs) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_feature_driver *drv = feature_drvs; + struct dfl_feature *feature; + int ret; + + while (drv->ops) { + dfl_fpga_dev_for_each_feature(pdata, feature) { + /* match feature and drv using id */ + if (feature->id == drv->id) { + ret = dfl_feature_instance_init(pdev, pdata, + feature, drv); + if (ret) + goto exit; + } + } + drv++; + } + + return 0; +exit: + dfl_fpga_dev_feature_uinit(pdev); + return ret; +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_init); + +static void dfl_chardev_uinit(void) +{ + int i; + + for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) + if (MAJOR(dfl_chrdevs[i].devt)) { + unregister_chrdev_region(dfl_chrdevs[i].devt, + MINORMASK); + dfl_chrdevs[i].devt = MKDEV(0, 0); + } +} + +static int dfl_chardev_init(void) +{ + int i, ret; + + for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) { + ret = alloc_chrdev_region(&dfl_chrdevs[i].devt, 0, MINORMASK, + dfl_chrdevs[i].name); + if (ret) + goto exit; + } + + return 0; + +exit: + dfl_chardev_uinit(); + return ret; +} + +static dev_t dfl_get_devt(enum dfl_fpga_devt_type type, int id) +{ + if (type >= DFL_FPGA_DEVT_MAX) + return 0; + + return MKDEV(MAJOR(dfl_chrdevs[type].devt), id); +} + +/** + * dfl_fpga_dev_ops_register - register cdev ops for feature dev + * + * @pdev: feature dev. + * @fops: file operations for feature dev's cdev. + * @owner: owning module/driver. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_dev_ops_register(struct platform_device *pdev, + const struct file_operations *fops, + struct module *owner) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + + cdev_init(&pdata->cdev, fops); + pdata->cdev.owner = owner; + + /* + * set parent to the feature device so that its refcount is + * decreased after the last refcount of cdev is gone, that + * makes sure the feature device is valid during device + * file's life-cycle. + */ + pdata->cdev.kobj.parent = &pdev->dev.kobj; + + return cdev_add(&pdata->cdev, pdev->dev.devt, 1); +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_register); + +/** + * dfl_fpga_dev_ops_unregister - unregister cdev ops for feature dev + * @pdev: feature dev. + */ +void dfl_fpga_dev_ops_unregister(struct platform_device *pdev) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + + cdev_del(&pdata->cdev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_unregister); + +/** + * struct build_feature_devs_info - info collected during feature dev build. + * + * @dev: device to enumerate. + * @cdev: the container device for all feature devices. + * @feature_dev: current feature device. + * @ioaddr: header register region address of feature device in enumeration. + * @sub_features: a sub features linked list for feature device in enumeration. + * @feature_num: number of sub features for feature device in enumeration. + */ +struct build_feature_devs_info { + struct device *dev; + struct dfl_fpga_cdev *cdev; + struct platform_device *feature_dev; + void __iomem *ioaddr; + struct list_head sub_features; + int feature_num; +}; + +/** + * struct dfl_feature_info - sub feature info collected during feature dev build + * + * @fid: id of this sub feature. + * @mmio_res: mmio resource of this sub feature. + * @ioaddr: mapped base address of mmio resource. + * @node: node in sub_features linked list. + */ +struct dfl_feature_info { + u64 fid; + struct resource mmio_res; + void __iomem *ioaddr; + struct list_head node; +}; + +static void dfl_fpga_cdev_add_port_dev(struct dfl_fpga_cdev *cdev, + struct platform_device *port) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&port->dev); + + mutex_lock(&cdev->lock); + list_add(&pdata->node, &cdev->port_dev_list); + get_device(&pdata->dev->dev); + mutex_unlock(&cdev->lock); +} + +/* + * register current feature device, it is called when we need to switch to + * another feature parsing or we have parsed all features on given device + * feature list. + */ +static int build_info_commit_dev(struct build_feature_devs_info *binfo) +{ + struct platform_device *fdev = binfo->feature_dev; + struct dfl_feature_platform_data *pdata; + struct dfl_feature_info *finfo, *p; + int ret, index = 0; + + if (!fdev) + return 0; + + /* + * we do not need to care for the memory which is associated with + * the platform device. After calling platform_device_unregister(), + * it will be automatically freed by device's release() callback, + * platform_device_release(). + */ + pdata = kzalloc(dfl_feature_platform_data_size(binfo->feature_num), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->dev = fdev; + pdata->num = binfo->feature_num; + pdata->dfl_cdev = binfo->cdev; + mutex_init(&pdata->lock); + + /* + * the count should be initialized to 0 to make sure + *__fpga_port_enable() following __fpga_port_disable() + * works properly for port device. + * and it should always be 0 for fme device. + */ + WARN_ON(pdata->disable_count); + + fdev->dev.platform_data = pdata; + + /* each sub feature has one MMIO resource */ + fdev->num_resources = binfo->feature_num; + fdev->resource = kcalloc(binfo->feature_num, sizeof(*fdev->resource), + GFP_KERNEL); + if (!fdev->resource) + return -ENOMEM; + + /* fill features and resource information for feature dev */ + list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { + struct dfl_feature *feature = &pdata->features[index]; + + /* save resource information for each feature */ + feature->id = finfo->fid; + feature->resource_index = index; + feature->ioaddr = finfo->ioaddr; + fdev->resource[index++] = finfo->mmio_res; + + list_del(&finfo->node); + kfree(finfo); + } + + ret = platform_device_add(binfo->feature_dev); + if (!ret) { + if (feature_dev_id_type(binfo->feature_dev) == PORT_ID) + dfl_fpga_cdev_add_port_dev(binfo->cdev, + binfo->feature_dev); + else + binfo->cdev->fme_dev = + get_device(&binfo->feature_dev->dev); + /* + * reset it to avoid build_info_free() freeing their resource. + * + * The resource of successfully registered feature devices + * will be freed by platform_device_unregister(). See the + * comments in build_info_create_dev(). + */ + binfo->feature_dev = NULL; + } + + return ret; +} + +static int +build_info_create_dev(struct build_feature_devs_info *binfo, + enum dfl_id_type type, void __iomem *ioaddr) +{ + struct platform_device *fdev; + int ret; + + if (type >= DFL_ID_MAX) + return -EINVAL; + + /* we will create a new device, commit current device first */ + ret = build_info_commit_dev(binfo); + if (ret) + return ret; + + /* + * we use -ENODEV as the initialization indicator which indicates + * whether the id need to be reclaimed + */ + fdev = platform_device_alloc(dfl_devs[type].name, -ENODEV); + if (!fdev) + return -ENOMEM; + + binfo->feature_dev = fdev; + binfo->feature_num = 0; + binfo->ioaddr = ioaddr; + INIT_LIST_HEAD(&binfo->sub_features); + + fdev->id = dfl_id_alloc(type, &fdev->dev); + if (fdev->id < 0) + return fdev->id; + + fdev->dev.parent = &binfo->cdev->region->dev; + fdev->dev.devt = dfl_get_devt(dfl_devs[type].devt_type, fdev->id); + + return 0; +} + +static void build_info_free(struct build_feature_devs_info *binfo) +{ + struct dfl_feature_info *finfo, *p; + + /* + * it is a valid id, free it. See comments in + * build_info_create_dev() + */ + if (binfo->feature_dev && binfo->feature_dev->id >= 0) { + dfl_id_free(feature_dev_id_type(binfo->feature_dev), + binfo->feature_dev->id); + + list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { + list_del(&finfo->node); + kfree(finfo); + } + } + + platform_device_put(binfo->feature_dev); + + devm_kfree(binfo->dev, binfo); +} + +static inline u32 feature_size(void __iomem *start) +{ + u64 v = readq(start + DFH); + u32 ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); + /* workaround for private features with invalid size, use 4K instead */ + return ofst ? ofst : 4096; +} + +static u64 feature_id(void __iomem *start) +{ + u64 v = readq(start + DFH); + u16 id = FIELD_GET(DFH_ID, v); + u8 type = FIELD_GET(DFH_TYPE, v); + + if (type == DFH_TYPE_FIU) + return FEATURE_ID_FIU_HEADER; + else if (type == DFH_TYPE_PRIVATE) + return id; + else if (type == DFH_TYPE_AFU) + return FEATURE_ID_AFU; + + WARN_ON(1); + return 0; +} + +/* + * when create sub feature instances, for private features, it doesn't need + * to provide resource size and feature id as they could be read from DFH + * register. For afu sub feature, its register region only contains user + * defined registers, so never trust any information from it, just use the + * resource size information provided by its parent FIU. + */ +static int +create_feature_instance(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst, + resource_size_t size, u64 fid) +{ + struct dfl_feature_info *finfo; + + /* read feature size and id if inputs are invalid */ + size = size ? size : feature_size(dfl->ioaddr + ofst); + fid = fid ? fid : feature_id(dfl->ioaddr + ofst); + + if (dfl->len - ofst < size) + return -EINVAL; + + finfo = kzalloc(sizeof(*finfo), GFP_KERNEL); + if (!finfo) + return -ENOMEM; + + finfo->fid = fid; + finfo->mmio_res.start = dfl->start + ofst; + finfo->mmio_res.end = finfo->mmio_res.start + size - 1; + finfo->mmio_res.flags = IORESOURCE_MEM; + finfo->ioaddr = dfl->ioaddr + ofst; + + list_add_tail(&finfo->node, &binfo->sub_features); + binfo->feature_num++; + + return 0; +} + +static int parse_feature_port_afu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + u64 v = readq(binfo->ioaddr + PORT_HDR_CAP); + u32 size = FIELD_GET(PORT_CAP_MMIO_SIZE, v) << 10; + + WARN_ON(!size); + + return create_feature_instance(binfo, dfl, ofst, size, FEATURE_ID_AFU); +} + +static int parse_feature_afu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + if (!binfo->feature_dev) { + dev_err(binfo->dev, "this AFU does not belong to any FIU.\n"); + return -EINVAL; + } + + switch (feature_dev_id_type(binfo->feature_dev)) { + case PORT_ID: + return parse_feature_port_afu(binfo, dfl, ofst); + default: + dev_info(binfo->dev, "AFU belonging to FIU %s is not supported yet.\n", + binfo->feature_dev->name); + } + + return 0; +} + +static int parse_feature_fiu(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + u32 id, offset; + u64 v; + int ret = 0; + + v = readq(dfl->ioaddr + ofst + DFH); + id = FIELD_GET(DFH_ID, v); + + /* create platform device for dfl feature dev */ + ret = build_info_create_dev(binfo, dfh_id_to_type(id), + dfl->ioaddr + ofst); + if (ret) + return ret; + + ret = create_feature_instance(binfo, dfl, ofst, 0, 0); + if (ret) + return ret; + /* + * find and parse FIU's child AFU via its NEXT_AFU register. + * please note that only Port has valid NEXT_AFU pointer per spec. + */ + v = readq(dfl->ioaddr + ofst + NEXT_AFU); + + offset = FIELD_GET(NEXT_AFU_NEXT_DFH_OFST, v); + if (offset) + return parse_feature_afu(binfo, dfl, ofst + offset); + + dev_dbg(binfo->dev, "No AFUs detected on FIU %d\n", id); + + return ret; +} + +static int parse_feature_private(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, + resource_size_t ofst) +{ + if (!binfo->feature_dev) { + dev_err(binfo->dev, "the private feature %llx does not belong to any AFU.\n", + (unsigned long long)feature_id(dfl->ioaddr + ofst)); + return -EINVAL; + } + + return create_feature_instance(binfo, dfl, ofst, 0, 0); +} + +/** + * parse_feature - parse a feature on given device feature list + * + * @binfo: build feature devices information. + * @dfl: device feature list to parse + * @ofst: offset to feature header on this device feature list + */ +static int parse_feature(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) +{ + u64 v; + u32 type; + + v = readq(dfl->ioaddr + ofst + DFH); + type = FIELD_GET(DFH_TYPE, v); + + switch (type) { + case DFH_TYPE_AFU: + return parse_feature_afu(binfo, dfl, ofst); + case DFH_TYPE_PRIVATE: + return parse_feature_private(binfo, dfl, ofst); + case DFH_TYPE_FIU: + return parse_feature_fiu(binfo, dfl, ofst); + default: + dev_info(binfo->dev, + "Feature Type %x is not supported.\n", type); + } + + return 0; +} + +static int parse_feature_list(struct build_feature_devs_info *binfo, + struct dfl_fpga_enum_dfl *dfl) +{ + void __iomem *start = dfl->ioaddr; + void __iomem *end = dfl->ioaddr + dfl->len; + int ret = 0; + u32 ofst = 0; + u64 v; + + /* walk through the device feature list via DFH's next DFH pointer. */ + for (; start < end; start += ofst) { + if (end - start < DFH_SIZE) { + dev_err(binfo->dev, "The region is too small to contain a feature.\n"); + return -EINVAL; + } + + ret = parse_feature(binfo, dfl, start - dfl->ioaddr); + if (ret) + return ret; + + v = readq(start + DFH); + ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); + + /* stop parsing if EOL(End of List) is set or offset is 0 */ + if ((v & DFH_EOL) || !ofst) + break; + } + + /* commit current feature device when reach the end of list */ + return build_info_commit_dev(binfo); +} + +struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev) +{ + struct dfl_fpga_enum_info *info; + + get_device(dev); + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) { + put_device(dev); + return NULL; + } + + info->dev = dev; + INIT_LIST_HEAD(&info->dfls); + + return info; +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_alloc); + +void dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info) +{ + struct dfl_fpga_enum_dfl *tmp, *dfl; + struct device *dev; + + if (!info) + return; + + dev = info->dev; + + /* remove all device feature lists in the list. */ + list_for_each_entry_safe(dfl, tmp, &info->dfls, node) { + list_del(&dfl->node); + devm_kfree(dev, dfl); + } + + devm_kfree(dev, info); + put_device(dev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_free); + +/** + * dfl_fpga_enum_info_add_dfl - add info of a device feature list to enum info + * + * @info: ptr to dfl_fpga_enum_info + * @start: mmio resource address of the device feature list. + * @len: mmio resource length of the device feature list. + * @ioaddr: mapped mmio resource address of the device feature list. + * + * One FPGA device may have one or more Device Feature Lists (DFLs), use this + * function to add information of each DFL to common data structure for next + * step enumeration. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, + resource_size_t start, resource_size_t len, + void __iomem *ioaddr) +{ + struct dfl_fpga_enum_dfl *dfl; + + dfl = devm_kzalloc(info->dev, sizeof(*dfl), GFP_KERNEL); + if (!dfl) + return -ENOMEM; + + dfl->start = start; + dfl->len = len; + dfl->ioaddr = ioaddr; + + list_add_tail(&dfl->node, &info->dfls); + + return 0; +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_add_dfl); + +static int remove_feature_dev(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + enum dfl_id_type type = feature_dev_id_type(pdev); + int id = pdev->id; + + platform_device_unregister(pdev); + + dfl_id_free(type, id); + + return 0; +} + +static void remove_feature_devs(struct dfl_fpga_cdev *cdev) +{ + device_for_each_child(&cdev->region->dev, NULL, remove_feature_dev); +} + +/** + * dfl_fpga_feature_devs_enumerate - enumerate feature devices + * @info: information for enumeration. + * + * This function creates a container device (base FPGA region), enumerates + * feature devices based on the enumeration info and creates platform devices + * under the container device. + * + * Return: dfl_fpga_cdev struct on success, -errno on failure + */ +struct dfl_fpga_cdev * +dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info) +{ + struct build_feature_devs_info *binfo; + struct dfl_fpga_enum_dfl *dfl; + struct dfl_fpga_cdev *cdev; + int ret = 0; + + if (!info->dev) + return ERR_PTR(-ENODEV); + + cdev = devm_kzalloc(info->dev, sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return ERR_PTR(-ENOMEM); + + cdev->region = fpga_region_create(info->dev, NULL, NULL); + if (!cdev->region) { + ret = -ENOMEM; + goto free_cdev_exit; + } + + cdev->parent = info->dev; + mutex_init(&cdev->lock); + INIT_LIST_HEAD(&cdev->port_dev_list); + + ret = fpga_region_register(cdev->region); + if (ret) + goto free_region_exit; + + /* create and init build info for enumeration */ + binfo = devm_kzalloc(info->dev, sizeof(*binfo), GFP_KERNEL); + if (!binfo) { + ret = -ENOMEM; + goto unregister_region_exit; + } + + binfo->dev = info->dev; + binfo->cdev = cdev; + + /* + * start enumeration for all feature devices based on Device Feature + * Lists. + */ + list_for_each_entry(dfl, &info->dfls, node) { + ret = parse_feature_list(binfo, dfl); + if (ret) { + remove_feature_devs(cdev); + build_info_free(binfo); + goto unregister_region_exit; + } + } + + build_info_free(binfo); + + return cdev; + +unregister_region_exit: + fpga_region_unregister(cdev->region); +free_region_exit: + fpga_region_free(cdev->region); +free_cdev_exit: + devm_kfree(info->dev, cdev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_enumerate); + +/** + * dfl_fpga_feature_devs_remove - remove all feature devices + * @cdev: fpga container device. + * + * Remove the container device and all feature devices under given container + * devices. + */ +void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev) +{ + struct dfl_feature_platform_data *pdata, *ptmp; + + remove_feature_devs(cdev); + + mutex_lock(&cdev->lock); + if (cdev->fme_dev) { + /* the fme should be unregistered. */ + WARN_ON(device_is_registered(cdev->fme_dev)); + put_device(cdev->fme_dev); + } + + list_for_each_entry_safe(pdata, ptmp, &cdev->port_dev_list, node) { + struct platform_device *port_dev = pdata->dev; + + /* the port should be unregistered. */ + WARN_ON(device_is_registered(&port_dev->dev)); + list_del(&pdata->node); + put_device(&port_dev->dev); + } + mutex_unlock(&cdev->lock); + + fpga_region_unregister(cdev->region); + devm_kfree(cdev->parent, cdev); +} +EXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_remove); + +/** + * __dfl_fpga_cdev_find_port - find a port under given container device + * + * @cdev: container device + * @data: data passed to match function + * @match: match function used to find specific port from the port device list + * + * Find a port device under container device. This function needs to be + * invoked with lock held. + * + * Return: pointer to port's platform device if successful, NULL otherwise. + * + * NOTE: you will need to drop the device reference with put_device() after use. + */ +struct platform_device * +__dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)) +{ + struct dfl_feature_platform_data *pdata; + struct platform_device *port_dev; + + list_for_each_entry(pdata, &cdev->port_dev_list, node) { + port_dev = pdata->dev; + + if (match(port_dev, data) && get_device(&port_dev->dev)) + return port_dev; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(__dfl_fpga_cdev_find_port); + +static int __init dfl_fpga_init(void) +{ + int ret; + + dfl_ids_init(); + + ret = dfl_chardev_init(); + if (ret) + dfl_ids_destroy(); + + return ret; +} + +static void __exit dfl_fpga_exit(void) +{ + dfl_chardev_uinit(); + dfl_ids_destroy(); +} + +module_init(dfl_fpga_init); +module_exit(dfl_fpga_exit); + +MODULE_DESCRIPTION("FPGA Device Feature List (DFL) Support"); +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h new file mode 100644 index 000000000000..a8b869e9e5b7 --- /dev/null +++ b/drivers/fpga/dfl.h @@ -0,0 +1,410 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Driver Header File for FPGA Device Feature List (DFL) Support + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei <luwei.kang@intel.com> + * Zhang Yi <yi.z.zhang@intel.com> + * Wu Hao <hao.wu@intel.com> + * Xiao Guangrong <guangrong.xiao@linux.intel.com> + */ + +#ifndef __FPGA_DFL_H +#define __FPGA_DFL_H + +#include <linux/bitfield.h> +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/iopoll.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/uuid.h> +#include <linux/fpga/fpga-region.h> + +/* maximum supported number of ports */ +#define MAX_DFL_FPGA_PORT_NUM 4 +/* plus one for fme device */ +#define MAX_DFL_FEATURE_DEV_NUM (MAX_DFL_FPGA_PORT_NUM + 1) + +/* Reserved 0x0 for Header Group Register and 0xff for AFU */ +#define FEATURE_ID_FIU_HEADER 0x0 +#define FEATURE_ID_AFU 0xff + +#define FME_FEATURE_ID_HEADER FEATURE_ID_FIU_HEADER +#define FME_FEATURE_ID_THERMAL_MGMT 0x1 +#define FME_FEATURE_ID_POWER_MGMT 0x2 +#define FME_FEATURE_ID_GLOBAL_IPERF 0x3 +#define FME_FEATURE_ID_GLOBAL_ERR 0x4 +#define FME_FEATURE_ID_PR_MGMT 0x5 +#define FME_FEATURE_ID_HSSI 0x6 +#define FME_FEATURE_ID_GLOBAL_DPERF 0x7 + +#define PORT_FEATURE_ID_HEADER FEATURE_ID_FIU_HEADER +#define PORT_FEATURE_ID_AFU FEATURE_ID_AFU +#define PORT_FEATURE_ID_ERROR 0x10 +#define PORT_FEATURE_ID_UMSG 0x11 +#define PORT_FEATURE_ID_UINT 0x12 +#define PORT_FEATURE_ID_STP 0x13 + +/* + * Device Feature Header Register Set + * + * For FIUs, they all have DFH + GUID + NEXT_AFU as common header registers. + * For AFUs, they have DFH + GUID as common header registers. + * For private features, they only have DFH register as common header. + */ +#define DFH 0x0 +#define GUID_L 0x8 +#define GUID_H 0x10 +#define NEXT_AFU 0x18 + +#define DFH_SIZE 0x8 + +/* Device Feature Header Register Bitfield */ +#define DFH_ID GENMASK_ULL(11, 0) /* Feature ID */ +#define DFH_ID_FIU_FME 0 +#define DFH_ID_FIU_PORT 1 +#define DFH_REVISION GENMASK_ULL(15, 12) /* Feature revision */ +#define DFH_NEXT_HDR_OFST GENMASK_ULL(39, 16) /* Offset to next DFH */ +#define DFH_EOL BIT_ULL(40) /* End of list */ +#define DFH_TYPE GENMASK_ULL(63, 60) /* Feature type */ +#define DFH_TYPE_AFU 1 +#define DFH_TYPE_PRIVATE 3 +#define DFH_TYPE_FIU 4 + +/* Next AFU Register Bitfield */ +#define NEXT_AFU_NEXT_DFH_OFST GENMASK_ULL(23, 0) /* Offset to next AFU */ + +/* FME Header Register Set */ +#define FME_HDR_DFH DFH +#define FME_HDR_GUID_L GUID_L +#define FME_HDR_GUID_H GUID_H +#define FME_HDR_NEXT_AFU NEXT_AFU +#define FME_HDR_CAP 0x30 +#define FME_HDR_PORT_OFST(n) (0x38 + ((n) * 0x8)) +#define FME_HDR_BITSTREAM_ID 0x60 +#define FME_HDR_BITSTREAM_MD 0x68 + +/* FME Fab Capability Register Bitfield */ +#define FME_CAP_FABRIC_VERID GENMASK_ULL(7, 0) /* Fabric version ID */ +#define FME_CAP_SOCKET_ID BIT_ULL(8) /* Socket ID */ +#define FME_CAP_PCIE0_LINK_AVL BIT_ULL(12) /* PCIE0 Link */ +#define FME_CAP_PCIE1_LINK_AVL BIT_ULL(13) /* PCIE1 Link */ +#define FME_CAP_COHR_LINK_AVL BIT_ULL(14) /* Coherent Link */ +#define FME_CAP_IOMMU_AVL BIT_ULL(16) /* IOMMU available */ +#define FME_CAP_NUM_PORTS GENMASK_ULL(19, 17) /* Number of ports */ +#define FME_CAP_ADDR_WIDTH GENMASK_ULL(29, 24) /* Address bus width */ +#define FME_CAP_CACHE_SIZE GENMASK_ULL(43, 32) /* cache size in KB */ +#define FME_CAP_CACHE_ASSOC GENMASK_ULL(47, 44) /* Associativity */ + +/* FME Port Offset Register Bitfield */ +/* Offset to port device feature header */ +#define FME_PORT_OFST_DFH_OFST GENMASK_ULL(23, 0) +/* PCI Bar ID for this port */ +#define FME_PORT_OFST_BAR_ID GENMASK_ULL(34, 32) +/* AFU MMIO access permission. 1 - VF, 0 - PF. */ +#define FME_PORT_OFST_ACC_CTRL BIT_ULL(55) +#define FME_PORT_OFST_ACC_PF 0 +#define FME_PORT_OFST_ACC_VF 1 +#define FME_PORT_OFST_IMP BIT_ULL(60) + +/* PORT Header Register Set */ +#define PORT_HDR_DFH DFH +#define PORT_HDR_GUID_L GUID_L +#define PORT_HDR_GUID_H GUID_H +#define PORT_HDR_NEXT_AFU NEXT_AFU +#define PORT_HDR_CAP 0x30 +#define PORT_HDR_CTRL 0x38 + +/* Port Capability Register Bitfield */ +#define PORT_CAP_PORT_NUM GENMASK_ULL(1, 0) /* ID of this port */ +#define PORT_CAP_MMIO_SIZE GENMASK_ULL(23, 8) /* MMIO size in KB */ +#define PORT_CAP_SUPP_INT_NUM GENMASK_ULL(35, 32) /* Interrupts num */ + +/* Port Control Register Bitfield */ +#define PORT_CTRL_SFTRST BIT_ULL(0) /* Port soft reset */ +/* Latency tolerance reporting. '1' >= 40us, '0' < 40us.*/ +#define PORT_CTRL_LATENCY BIT_ULL(2) +#define PORT_CTRL_SFTRST_ACK BIT_ULL(4) /* HW ack for reset */ +/** + * struct dfl_fpga_port_ops - port ops + * + * @name: name of this port ops, to match with port platform device. + * @owner: pointer to the module which owns this port ops. + * @node: node to link port ops to global list. + * @get_id: get port id from hardware. + * @enable_set: enable/disable the port. + */ +struct dfl_fpga_port_ops { + const char *name; + struct module *owner; + struct list_head node; + int (*get_id)(struct platform_device *pdev); + int (*enable_set)(struct platform_device *pdev, bool enable); +}; + +void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops); +void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops); +struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev); +void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops); +int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id); + +/** + * struct dfl_feature_driver - sub feature's driver + * + * @id: sub feature id. + * @ops: ops of this sub feature. + */ +struct dfl_feature_driver { + u64 id; + const struct dfl_feature_ops *ops; +}; + +/** + * struct dfl_feature - sub feature of the feature devices + * + * @id: sub feature id. + * @resource_index: each sub feature has one mmio resource for its registers. + * this index is used to find its mmio resource from the + * feature dev (platform device)'s reources. + * @ioaddr: mapped mmio resource address. + * @ops: ops of this sub feature. + */ +struct dfl_feature { + u64 id; + int resource_index; + void __iomem *ioaddr; + const struct dfl_feature_ops *ops; +}; + +#define DEV_STATUS_IN_USE 0 + +/** + * struct dfl_feature_platform_data - platform data for feature devices + * + * @node: node to link feature devs to container device's port_dev_list. + * @lock: mutex to protect platform data. + * @cdev: cdev of feature dev. + * @dev: ptr to platform device linked with this platform data. + * @dfl_cdev: ptr to container device. + * @disable_count: count for port disable. + * @num: number for sub features. + * @dev_status: dev status (e.g. DEV_STATUS_IN_USE). + * @private: ptr to feature dev private data. + * @features: sub features of this feature dev. + */ +struct dfl_feature_platform_data { + struct list_head node; + struct mutex lock; + struct cdev cdev; + struct platform_device *dev; + struct dfl_fpga_cdev *dfl_cdev; + unsigned int disable_count; + unsigned long dev_status; + void *private; + int num; + struct dfl_feature features[0]; +}; + +static inline +int dfl_feature_dev_use_begin(struct dfl_feature_platform_data *pdata) +{ + /* Test and set IN_USE flags to ensure file is exclusively used */ + if (test_and_set_bit_lock(DEV_STATUS_IN_USE, &pdata->dev_status)) + return -EBUSY; + + return 0; +} + +static inline +void dfl_feature_dev_use_end(struct dfl_feature_platform_data *pdata) +{ + clear_bit_unlock(DEV_STATUS_IN_USE, &pdata->dev_status); +} + +static inline +void dfl_fpga_pdata_set_private(struct dfl_feature_platform_data *pdata, + void *private) +{ + pdata->private = private; +} + +static inline +void *dfl_fpga_pdata_get_private(struct dfl_feature_platform_data *pdata) +{ + return pdata->private; +} + +struct dfl_feature_ops { + int (*init)(struct platform_device *pdev, struct dfl_feature *feature); + void (*uinit)(struct platform_device *pdev, + struct dfl_feature *feature); + long (*ioctl)(struct platform_device *pdev, struct dfl_feature *feature, + unsigned int cmd, unsigned long arg); +}; + +#define DFL_FPGA_FEATURE_DEV_FME "dfl-fme" +#define DFL_FPGA_FEATURE_DEV_PORT "dfl-port" + +static inline int dfl_feature_platform_data_size(const int num) +{ + return sizeof(struct dfl_feature_platform_data) + + num * sizeof(struct dfl_feature); +} + +void dfl_fpga_dev_feature_uinit(struct platform_device *pdev); +int dfl_fpga_dev_feature_init(struct platform_device *pdev, + struct dfl_feature_driver *feature_drvs); + +int dfl_fpga_dev_ops_register(struct platform_device *pdev, + const struct file_operations *fops, + struct module *owner); +void dfl_fpga_dev_ops_unregister(struct platform_device *pdev); + +static inline +struct platform_device *dfl_fpga_inode_to_feature_dev(struct inode *inode) +{ + struct dfl_feature_platform_data *pdata; + + pdata = container_of(inode->i_cdev, struct dfl_feature_platform_data, + cdev); + return pdata->dev; +} + +#define dfl_fpga_dev_for_each_feature(pdata, feature) \ + for ((feature) = (pdata)->features; \ + (feature) < (pdata)->features + (pdata)->num; (feature)++) + +static inline +struct dfl_feature *dfl_get_feature_by_id(struct device *dev, u64 id) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); + struct dfl_feature *feature; + + dfl_fpga_dev_for_each_feature(pdata, feature) + if (feature->id == id) + return feature; + + return NULL; +} + +static inline +void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u64 id) +{ + struct dfl_feature *feature = dfl_get_feature_by_id(dev, id); + + if (feature && feature->ioaddr) + return feature->ioaddr; + + WARN_ON(1); + return NULL; +} + +static inline bool is_dfl_feature_present(struct device *dev, u64 id) +{ + return !!dfl_get_feature_ioaddr_by_id(dev, id); +} + +static inline +struct device *dfl_fpga_pdata_to_parent(struct dfl_feature_platform_data *pdata) +{ + return pdata->dev->dev.parent->parent; +} + +static inline bool dfl_feature_is_fme(void __iomem *base) +{ + u64 v = readq(base + DFH); + + return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) && + (FIELD_GET(DFH_ID, v) == DFH_ID_FIU_FME); +} + +static inline bool dfl_feature_is_port(void __iomem *base) +{ + u64 v = readq(base + DFH); + + return (FIELD_GET(DFH_TYPE, v) == DFH_TYPE_FIU) && + (FIELD_GET(DFH_ID, v) == DFH_ID_FIU_PORT); +} + +/** + * struct dfl_fpga_enum_info - DFL FPGA enumeration information + * + * @dev: parent device. + * @dfls: list of device feature lists. + */ +struct dfl_fpga_enum_info { + struct device *dev; + struct list_head dfls; +}; + +/** + * struct dfl_fpga_enum_dfl - DFL FPGA enumeration device feature list info + * + * @start: base address of this device feature list. + * @len: size of this device feature list. + * @ioaddr: mapped base address of this device feature list. + * @node: node in list of device feature lists. + */ +struct dfl_fpga_enum_dfl { + resource_size_t start; + resource_size_t len; + + void __iomem *ioaddr; + + struct list_head node; +}; + +struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev); +int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, + resource_size_t start, resource_size_t len, + void __iomem *ioaddr); +void dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info); + +/** + * struct dfl_fpga_cdev - container device of DFL based FPGA + * + * @parent: parent device of this container device. + * @region: base fpga region. + * @fme_dev: FME feature device under this container device. + * @lock: mutex lock to protect the port device list. + * @port_dev_list: list of all port feature devices under this container device. + */ +struct dfl_fpga_cdev { + struct device *parent; + struct fpga_region *region; + struct device *fme_dev; + struct mutex lock; + struct list_head port_dev_list; +}; + +struct dfl_fpga_cdev * +dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info); +void dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev); + +/* + * need to drop the device reference with put_device() after use port platform + * device returned by __dfl_fpga_cdev_find_port and dfl_fpga_cdev_find_port + * functions. + */ +struct platform_device * +__dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)); + +static inline struct platform_device * +dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, + int (*match)(struct platform_device *, void *)) +{ + struct platform_device *pdev; + + mutex_lock(&cdev->lock); + pdev = __dfl_fpga_cdev_find_port(cdev, data, match); + mutex_unlock(&cdev->lock); + + return pdev; +} +#endif /* __FPGA_DFL_H */ diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index c1564cf827fe..a41b07e37884 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -406,12 +406,40 @@ static ssize_t state_show(struct device *dev, return sprintf(buf, "%s\n", state_str[mgr->state]); } +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_manager *mgr = to_fpga_manager(dev); + u64 status; + int len = 0; + + if (!mgr->mops->status) + return -ENOENT; + + status = mgr->mops->status(mgr); + + if (status & FPGA_MGR_STATUS_OPERATION_ERR) + len += sprintf(buf + len, "reconfig operation error\n"); + if (status & FPGA_MGR_STATUS_CRC_ERR) + len += sprintf(buf + len, "reconfig CRC error\n"); + if (status & FPGA_MGR_STATUS_INCOMPATIBLE_IMAGE_ERR) + len += sprintf(buf + len, "reconfig incompatible image\n"); + if (status & FPGA_MGR_STATUS_IP_PROTOCOL_ERR) + len += sprintf(buf + len, "reconfig IP protocol error\n"); + if (status & FPGA_MGR_STATUS_FIFO_OVERFLOW_ERR) + len += sprintf(buf + len, "reconfig fifo overflow error\n"); + + return len; +} + static DEVICE_ATTR_RO(name); static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RO(status); static struct attribute *fpga_mgr_attrs[] = { &dev_attr_name.attr, &dev_attr_state.attr, + &dev_attr_status.attr, NULL, }; ATTRIBUTE_GROUPS(fpga_mgr); diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 6d214d75c7be..0d65220d5ec5 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -158,6 +158,27 @@ err_put_region: } EXPORT_SYMBOL_GPL(fpga_region_program_fpga); +static ssize_t compat_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fpga_region *region = to_fpga_region(dev); + + if (!region->compat_id) + return -ENOENT; + + return sprintf(buf, "%016llx%016llx\n", + (unsigned long long)region->compat_id->id_h, + (unsigned long long)region->compat_id->id_l); +} + +static DEVICE_ATTR_RO(compat_id); + +static struct attribute *fpga_region_attrs[] = { + &dev_attr_compat_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fpga_region); + /** * fpga_region_create - alloc and init a struct fpga_region * @dev: device parent @@ -258,6 +279,7 @@ static int __init fpga_region_init(void) if (IS_ERR(fpga_region_class)) return PTR_ERR(fpga_region_class); + fpga_region_class->dev_groups = fpga_region_groups; fpga_region_class->dev_release = fpga_region_dev_release; return 0; |