From 6b293258cded9c8ee44cce4081d9170d6d1b5f5d Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 12 Jun 2018 15:19:11 +1000 Subject: fsi: scom: Major overhaul This was too hard to split ... this adds a number of features to the SCOM user interface: - Support for indirect SCOMs - read()/write() interface now handle errors and retries - New ioctl() "raw" interface for use by debuggers Signed-off-by: Benjamin Herrenschmidt Reviewed-by: Eddie James Reviewed-by: Alistair Popple --- include/uapi/linux/fsi.h | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 include/uapi/linux/fsi.h (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/fsi.h b/include/uapi/linux/fsi.h new file mode 100644 index 000000000000..da577ecd90e7 --- /dev/null +++ b/include/uapi/linux/fsi.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_FSI_H +#define _UAPI_LINUX_FSI_H + +#include +#include + +/* + * /dev/scom "raw" ioctl interface + * + * The driver supports a high level "read/write" interface which + * handles retries and converts the status to Linux error codes, + * however low level tools an debugger need to access the "raw" + * HW status information and interpret it themselves, so this + * ioctl interface is also provided for their use case. + */ + +/* Structure for SCOM read/write */ +struct scom_access { + __u64 addr; /* SCOM address, supports indirect */ + __u64 data; /* SCOM data (in for write, out for read) */ + __u64 mask; /* Data mask for writes */ + __u32 intf_errors; /* Interface error flags */ +#define SCOM_INTF_ERR_PARITY 0x00000001 /* Parity error */ +#define SCOM_INTF_ERR_PROTECTION 0x00000002 /* Blocked by secure boot */ +#define SCOM_INTF_ERR_ABORT 0x00000004 /* PIB reset during access */ +#define SCOM_INTF_ERR_UNKNOWN 0x80000000 /* Unknown error */ + /* + * Note: Any other bit set in intf_errors need to be considered as an + * error. Future implementations may define new error conditions. The + * pib_status below is only valid if intf_errors is 0. + */ + __u8 pib_status; /* 3-bit PIB status */ +#define SCOM_PIB_SUCCESS 0 /* Access successful */ +#define SCOM_PIB_BLOCKED 1 /* PIB blocked, pls retry */ +#define SCOM_PIB_OFFLINE 2 /* Chiplet offline */ +#define SCOM_PIB_PARTIAL 3 /* Partial good */ +#define SCOM_PIB_BAD_ADDR 4 /* Invalid address */ +#define SCOM_PIB_CLK_ERR 5 /* Clock error */ +#define SCOM_PIB_PARITY_ERR 6 /* Parity error on the PIB bus */ +#define SCOM_PIB_TIMEOUT 7 /* Bus timeout */ + __u8 pad; +}; + +/* Flags for SCOM check */ +#define SCOM_CHECK_SUPPORTED 0x00000001 /* Interface supported */ +#define SCOM_CHECK_PROTECTED 0x00000002 /* Interface blocked by secure boot */ + +/* Flags for SCOM reset */ +#define SCOM_RESET_INTF 0x00000001 /* Reset interface */ +#define SCOM_RESET_PIB 0x00000002 /* Reset PIB */ + +#define FSI_SCOM_CHECK _IOR('s', 0x00, __u32) +#define FSI_SCOM_READ _IOWR('s', 0x01, struct scom_access) +#define FSI_SCOM_WRITE _IOWR('s', 0x02, struct scom_access) +#define FSI_SCOM_RESET _IOW('s', 0x03, __u32) + +#endif /* _UAPI_LINUX_FSI_H */ -- cgit v1.2.3-71-gd317 From 620e1902f6fe57ddacdabd9e33fadbd290be9652 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:23 +0800 Subject: fpga: dfl: fme: add DFL_FPGA_GET_API_VERSION/CHECK_EXTENSION ioctls support DFL_FPGA_GET_API_VERSION and DFL_FPGA_CHECK_EXTENSION ioctls are common ones which need to be supported by all feature devices drivers including FME and AFU. Userspace application can use these ioctl interfaces to get the API info and check if specific extension is supported or not in current driver. This patch implements above 2 ioctls in FPGA Management Engine (FME) driver. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Acked-by: Moritz Fischer Signed-off-by: Greg Kroah-Hartman --- Documentation/ioctl/ioctl-number.txt | 1 + drivers/fpga/dfl-fme-main.c | 12 +++++++++ include/uapi/linux/fpga-dfl.h | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 include/uapi/linux/fpga-dfl.h (limited to 'include/uapi/linux') diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 480c8609dc58..db9afea24299 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -322,6 +322,7 @@ Code Seq#(hex) Include File Comments 0xB3 00 linux/mmc/ioctl.h 0xB4 00-0F linux/gpio.h 0xB5 00-0F uapi/linux/rpmsg.h +0xB6 all linux/fpga-dfl.h 0xC0 00-0F linux/usb/iowarrior.h 0xCA 00-0F uapi/misc/cxl.h 0xCA 10-2F uapi/misc/ocxl.h diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c index c23c56fe3f4b..c83ff88e3bbb 100644 --- a/drivers/fpga/dfl-fme-main.c +++ b/drivers/fpga/dfl-fme-main.c @@ -16,6 +16,7 @@ #include #include +#include #include "dfl.h" @@ -116,6 +117,13 @@ static struct dfl_feature_driver fme_feature_drvs[] = { }, }; +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); @@ -156,6 +164,10 @@ static long fme_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 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. diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h new file mode 100644 index 000000000000..858e4437c31c --- /dev/null +++ b/include/uapi/linux/fpga-dfl.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Header File for FPGA DFL User API + * + * Copyright (C) 2017-2018 Intel Corporation, Inc. + * + * Authors: + * Kang Luwei + * Zhang Yi + * Wu Hao + * Xiao Guangrong + */ + +#ifndef _UAPI_LINUX_FPGA_DFL_H +#define _UAPI_LINUX_FPGA_DFL_H + +#include + +#define DFL_FPGA_API_VERSION 0 + +/* + * The IOCTL interface for DFL based FPGA is designed for extensibility by + * embedding the structure length (argsz) and flags into structures passed + * between kernel and userspace. This design referenced the VFIO IOCTL + * interface (include/uapi/linux/vfio.h). + */ + +#define DFL_FPGA_MAGIC 0xB6 + +#define DFL_FPGA_BASE 0 + +/** + * DFL_FPGA_GET_API_VERSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) + * + * Report the version of the driver API. + * Return: Driver API Version. + */ + +#define DFL_FPGA_GET_API_VERSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) + +/** + * DFL_FPGA_CHECK_EXTENSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) + * + * Check whether an extension is supported. + * Return: 0 if not supported, otherwise the extension is supported. + */ + +#define DFL_FPGA_CHECK_EXTENSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) + +#endif /* _UAPI_LINUX_FPGA_DFL_H */ -- cgit v1.2.3-71-gd317 From 29de76240e861d52b75405166337e94184f1875d Mon Sep 17 00:00:00 2001 From: Kang Luwei Date: Sat, 30 Jun 2018 08:53:24 +0800 Subject: fpga: dfl: fme: add partial reconfiguration sub feature support Partial Reconfiguration (PR) is the most important function for FME. It allows reconfiguration for given Port/Accelerated Function Unit (AFU). It creates platform devices for fpga-mgr, fpga-regions and fpga-bridges, and invokes fpga-region's interface (fpga_region_program_fpga) for PR operation once PR request received via ioctl. Below user space interface is exposed by this sub feature. Ioctl interface: * DFL_FPGA_FME_PORT_PR Do partial reconfiguration per information from userspace, including target port(AFU), buffer size and address info. It returns error code to userspace if failed. For detailed PR error information, user needs to read fpga-mgr's status sysfs interface. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Kang Luwei Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Makefile | 2 +- drivers/fpga/dfl-fme-main.c | 43 +++- drivers/fpga/dfl-fme-pr.c | 479 ++++++++++++++++++++++++++++++++++++++++++ drivers/fpga/dfl-fme-pr.h | 84 ++++++++ drivers/fpga/dfl-fme.h | 38 ++++ include/uapi/linux/fpga-dfl.h | 27 +++ 6 files changed, 671 insertions(+), 2 deletions(-) create mode 100644 drivers/fpga/dfl-fme-pr.c create mode 100644 drivers/fpga/dfl-fme-pr.h create mode 100644 drivers/fpga/dfl-fme.h (limited to 'include/uapi/linux') diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index db11f340ba0f..fd334d40aa1c 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -33,7 +33,7 @@ obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o obj-$(CONFIG_FPGA_DFL) += dfl.o obj-$(CONFIG_FPGA_DFL_FME) += dfl-fme.o -dfl-fme-objs := dfl-fme-main.o +dfl-fme-objs := dfl-fme-main.o dfl-fme-pr.o # Drivers for FPGAs which implement DFL obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-fme-main.c b/drivers/fpga/dfl-fme-main.c index c83ff88e3bbb..086ad2420ade 100644 --- a/drivers/fpga/dfl-fme-main.c +++ b/drivers/fpga/dfl-fme-main.c @@ -19,6 +19,7 @@ #include #include "dfl.h" +#include "dfl-fme.h" static ssize_t ports_num_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -112,6 +113,10 @@ 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, }, @@ -187,6 +192,35 @@ static long fme_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 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, @@ -198,10 +232,14 @@ static int fme_probe(struct platform_device *pdev) { int ret; - ret = dfl_fpga_dev_feature_init(pdev, fme_feature_drvs); + 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; @@ -210,6 +248,8 @@ static int fme_probe(struct platform_device *pdev) feature_uinit: dfl_fpga_dev_feature_uinit(pdev); +dev_destroy: + fme_dev_destroy(pdev); exit: return ret; } @@ -218,6 +258,7 @@ 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; } 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 + * Xiao Guangrong + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Christopher Rauer + * Henry Mitchel + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * Xiao Guangrong + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#ifndef __DFL_FME_PR_H +#define __DFL_FME_PR_H + +#include + +/** + * 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.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 + * Xiao Guangrong + * Wu Hao + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#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/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index 858e4437c31c..9666af85a8f5 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -14,6 +14,7 @@ #ifndef _UAPI_LINUX_FPGA_DFL_H #define _UAPI_LINUX_FPGA_DFL_H +#include #include #define DFL_FPGA_API_VERSION 0 @@ -28,6 +29,7 @@ #define DFL_FPGA_MAGIC 0xB6 #define DFL_FPGA_BASE 0 +#define DFL_FME_BASE 0x80 /** * DFL_FPGA_GET_API_VERSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) @@ -47,4 +49,29 @@ #define DFL_FPGA_CHECK_EXTENSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) +/* IOCTLs for FME file descriptor */ + +/** + * DFL_FPGA_FME_PORT_PR - _IOW(DFL_FPGA_MAGIC, DFL_FME_BASE + 0, + * struct dfl_fpga_fme_port_pr) + * + * Driver does Partial Reconfiguration based on Port ID and Buffer (Image) + * provided by caller. + * Return: 0 on success, -errno on failure. + * If DFL_FPGA_FME_PORT_PR returns -EIO, that indicates the HW has detected + * some errors during PR, under this case, the user can fetch HW error info + * from the status of FME's fpga manager. + */ + +struct dfl_fpga_fme_port_pr { + /* Input */ + __u32 argsz; /* Structure length */ + __u32 flags; /* Zero for now */ + __u32 port_id; + __u32 buffer_size; + __u64 buffer_address; /* Userspace address to the buffer for PR */ +}; + +#define DFL_FPGA_FME_PORT_PR _IO(DFL_FPGA_MAGIC, DFL_FME_BASE + 0) + #endif /* _UAPI_LINUX_FPGA_DFL_H */ -- cgit v1.2.3-71-gd317 From e4664c0ee4ac44993c62d10b048ab0a960691da5 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:32 +0800 Subject: fpga: dfl: afu: add header sub feature support The port header register set is always present for port, it is mainly for capability, control and status of the ports that AFU connected to. This patch implements header sub feature support. Below user interfaces are created by this patch. Sysfs interface: * /sys/class/fpga_region///id Read-only. Port ID. Ioctl interface: * DFL_FPGA_PORT_RESET Reset the FPGA Port and its AFU. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-platform-dfl-port | 7 ++ drivers/fpga/dfl-afu-main.c | 79 ++++++++++++++++++++++- include/uapi/linux/fpga-dfl.h | 17 +++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-platform-dfl-port (limited to 'include/uapi/linux') diff --git a/Documentation/ABI/testing/sysfs-platform-dfl-port b/Documentation/ABI/testing/sysfs-platform-dfl-port new file mode 100644 index 000000000000..cb91165f5397 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dfl-port @@ -0,0 +1,7 @@ +What: /sys/bus/platform/devices/dfl-port.0/id +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read-only. It returns id of this port. One DFL FPGA device + may have more than one port. Userspace could use this id to + distinguish different ports under same FPGA device. diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index a38d6a825e7e..d36b3e9f3984 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -16,6 +16,7 @@ #include #include +#include #include "dfl.h" @@ -87,6 +88,41 @@ static int port_disable(struct platform_device *pdev) 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; @@ -96,23 +132,63 @@ static int port_get_id(struct platform_device *pdev) 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"); - return 0; + 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 struct dfl_feature_driver port_feature_drvs[] = { @@ -154,6 +230,7 @@ static int afu_release(struct inode *inode, struct file *filp) pdata = dev_get_platdata(&pdev->dev); + port_reset(pdev); dfl_feature_dev_use_end(pdata); return 0; diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index 9666af85a8f5..e6b4dd26cc68 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -29,8 +29,11 @@ #define DFL_FPGA_MAGIC 0xB6 #define DFL_FPGA_BASE 0 +#define DFL_PORT_BASE 0x40 #define DFL_FME_BASE 0x80 +/* Common IOCTLs for both FME and AFU file descriptor */ + /** * DFL_FPGA_GET_API_VERSION - _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 0) * @@ -49,6 +52,20 @@ #define DFL_FPGA_CHECK_EXTENSION _IO(DFL_FPGA_MAGIC, DFL_FPGA_BASE + 1) +/* IOCTLs for AFU file descriptor */ + +/** + * DFL_FPGA_PORT_RESET - _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 0) + * + * Reset the FPGA Port and its AFU. No parameters are supported. + * Userspace can do Port reset at any time, e.g. during DMA or PR. 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. + * Return: 0 on success, -errno of failure + */ + +#define DFL_FPGA_PORT_RESET _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 0) + /* IOCTLs for FME file descriptor */ /** -- cgit v1.2.3-71-gd317 From 857a26222ff75eecf7d701ef0e91e4fbf6efa663 Mon Sep 17 00:00:00 2001 From: Xiao Guangrong Date: Sat, 30 Jun 2018 08:53:34 +0800 Subject: fpga: dfl: afu: add afu sub feature support User Accelerated Function Unit sub feature exposes the MMIO region of the AFU. After valid PR bitstream is programmed and the port is enabled, then this MMIO region could be accessed. This patch adds support to enumerate the AFU MMIO region and expose it to userspace via mmap file operation. Below interfaces are exposed to user: Sysfs interface: * /sys/class/fpga_region///afu_id Read-only. Indicate which PR bitstream is programmed to this AFU. Ioctl interfaces: * DFL_FPGA_PORT_GET_INFO Provide info to userspace on the number of supported region. Only UAFU region is supported now. * DFL_FPGA_PORT_GET_REGION_INFO Provide region information, including access permission, region size, offset from the start of device fd. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-platform-dfl-port | 9 + drivers/fpga/Makefile | 2 +- drivers/fpga/dfl-afu-main.c | 219 +++++++++++++++++++++- drivers/fpga/dfl-afu-region.c | 166 ++++++++++++++++ drivers/fpga/dfl-afu.h | 71 +++++++ include/uapi/linux/fpga-dfl.h | 48 +++++ 6 files changed, 508 insertions(+), 7 deletions(-) create mode 100644 drivers/fpga/dfl-afu-region.c create mode 100644 drivers/fpga/dfl-afu.h (limited to 'include/uapi/linux') diff --git a/Documentation/ABI/testing/sysfs-platform-dfl-port b/Documentation/ABI/testing/sysfs-platform-dfl-port index cb91165f5397..6a92dda517b0 100644 --- a/Documentation/ABI/testing/sysfs-platform-dfl-port +++ b/Documentation/ABI/testing/sysfs-platform-dfl-port @@ -5,3 +5,12 @@ Contact: Wu Hao Description: Read-only. It returns id of this port. One DFL FPGA device may have more than one port. Userspace could use this id to distinguish different ports under same FPGA device. + +What: /sys/bus/platform/devices/dfl-port.0/afu_id +Date: June 2018 +KernelVersion: 4.19 +Contact: Wu Hao +Description: Read-only. User can program different PR bitstreams to FPGA + Accelerator Function Unit (AFU) for different functions. It + returns uuid which could be used to identify which PR bitstream + is programmed in this AFU. diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 1ac7749b2542..a44d50dd0b70 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -38,7 +38,7 @@ 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-objs := dfl-afu-main.o dfl-afu-region.o # Drivers for FPGAs which implement DFL obj-$(CONFIG_FPGA_DFL_PCI) += dfl-pci.o diff --git a/drivers/fpga/dfl-afu-main.c b/drivers/fpga/dfl-afu-main.c index 4074b97122e2..f67a78d7e9ad 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -16,18 +16,18 @@ #include #include +#include #include -#include "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 User AFU is unable to respond to any MMIO access while in reset. - * port_enable function should only be used after port_disable - * function. + * 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) { @@ -191,11 +191,74 @@ static const struct dfl_feature_ops port_hdr_ops = { .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, } @@ -243,6 +306,64 @@ static long afu_ioctl_check_extension(struct dfl_feature_platform_data *pdata, 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(struct file *filp, unsigned int cmd, unsigned long arg) { struct platform_device *pdev = filp->private_data; @@ -259,6 +380,10 @@ static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 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); default: /* * Let sub-feature's ioctl function to handle the cmd @@ -277,13 +402,83 @@ static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 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); + 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); + 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); @@ -312,14 +507,25 @@ static int afu_probe(struct platform_device *pdev) 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) - return ret; + goto dev_destroy; ret = dfl_fpga_dev_ops_register(pdev, &afu_fops, THIS_MODULE); - if (ret) + if (ret) { dfl_fpga_dev_feature_uinit(pdev); + goto dev_destroy; + } + + return 0; +dev_destroy: + afu_dev_destroy(pdev); +exit: return ret; } @@ -329,6 +535,7 @@ static int afu_remove(struct platform_device *pdev) dfl_fpga_dev_ops_unregister(pdev); dfl_fpga_dev_feature_uinit(pdev); + afu_dev_destroy(pdev); return 0; } 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 + * Xiao Guangrong + */ +#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..11ce2cf99759 --- /dev/null +++ b/drivers/fpga/dfl-afu.h @@ -0,0 +1,71 @@ +/* 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 + * Xiao Guangrong + * Joseph Grecco + * Enno Luebbers + * Tim Whisonant + * Ananda Ravuri + * Henry Mitchel + */ + +#ifndef __DFL_AFU_H +#define __DFL_AFU_H + +#include + +#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 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. + * @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 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); +#endif diff --git a/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index e6b4dd26cc68..a3ccdfb115a5 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -66,6 +66,54 @@ #define DFL_FPGA_PORT_RESET _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 0) +/** + * DFL_FPGA_PORT_GET_INFO - _IOR(DFL_FPGA_MAGIC, DFL_PORT_BASE + 1, + * struct dfl_fpga_port_info) + * + * Retrieve information about the fpga port. + * Driver fills the info in provided struct dfl_fpga_port_info. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_info { + /* Input */ + __u32 argsz; /* Structure length */ + /* Output */ + __u32 flags; /* Zero for now */ + __u32 num_regions; /* The number of supported regions */ + __u32 num_umsgs; /* The number of allocated umsgs */ +}; + +#define DFL_FPGA_PORT_GET_INFO _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 1) + +/** + * FPGA_PORT_GET_REGION_INFO - _IOWR(FPGA_MAGIC, PORT_BASE + 2, + * struct dfl_fpga_port_region_info) + * + * Retrieve information about a device memory region. + * Caller provides struct dfl_fpga_port_region_info with index value set. + * Driver returns the region info in other fields. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_region_info { + /* input */ + __u32 argsz; /* Structure length */ + /* Output */ + __u32 flags; /* Access permission */ +#define DFL_PORT_REGION_READ (1 << 0) /* Region is readable */ +#define DFL_PORT_REGION_WRITE (1 << 1) /* Region is writable */ +#define DFL_PORT_REGION_MMAP (1 << 2) /* Can be mmaped to userspace */ + /* Input */ + __u32 index; /* Region index */ +#define DFL_PORT_REGION_INDEX_AFU 0 /* AFU */ +#define DFL_PORT_REGION_INDEX_STP 1 /* Signal Tap */ + __u32 padding; + /* Output */ + __u64 size; /* Region size (bytes) */ + __u64 offset; /* Region offset from start of device fd */ +}; + +#define DFL_FPGA_PORT_GET_REGION_INFO _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 2) + /* IOCTLs for FME file descriptor */ /** -- cgit v1.2.3-71-gd317 From fa8dda1edef9ebc3af467c644c5533ac97171e12 Mon Sep 17 00:00:00 2001 From: Wu Hao Date: Sat, 30 Jun 2018 08:53:35 +0800 Subject: fpga: dfl: afu: add DFL_FPGA_PORT_DMA_MAP/UNMAP ioctls support DMA memory regions are required for Accelerated Function Unit (AFU) usage. These two ioctls allow user space applications to map user memory regions for dma, and unmap them after use. Iova is returned from driver to user space application via DFL_FPGA_PORT_DMA_MAP ioctl. Application needs to unmap it after use, otherwise, driver will unmap them in device file release operation. Each AFU has its own rb tree to keep track of its mapped DMA regions. Ioctl interfaces: * DFL_FPGA_PORT_DMA_MAP Do the dma mapping per user_addr and length provided by user. Return iova in provided struct dfl_fpga_port_dma_map. * DFL_FPGA_PORT_DMA_UNMAP Unmap the dma region per iova provided by user. Signed-off-by: Tim Whisonant Signed-off-by: Enno Luebbers Signed-off-by: Shiva Rao Signed-off-by: Christopher Rauer Signed-off-by: Xiao Guangrong Signed-off-by: Wu Hao Acked-by: Alan Tull Signed-off-by: Greg Kroah-Hartman --- drivers/fpga/Makefile | 2 +- drivers/fpga/dfl-afu-dma-region.c | 463 ++++++++++++++++++++++++++++++++++++++ drivers/fpga/dfl-afu-main.c | 61 ++++- drivers/fpga/dfl-afu.h | 31 ++- include/uapi/linux/fpga-dfl.h | 37 +++ 5 files changed, 591 insertions(+), 3 deletions(-) create mode 100644 drivers/fpga/dfl-afu-dma-region.c (limited to 'include/uapi/linux') diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index a44d50dd0b70..7a2d73ba7122 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -38,7 +38,7 @@ 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-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 + * Xiao Guangrong + */ + +#include +#include +#include + +#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 index f67a78d7e9ad..02baa6a227c0 100644 --- a/drivers/fpga/dfl-afu-main.c +++ b/drivers/fpga/dfl-afu-main.c @@ -293,7 +293,11 @@ static int afu_release(struct inode *inode, struct file *filp) pdata = dev_get_platdata(&pdev->dev); - port_reset(pdev); + mutex_lock(&pdata->lock); + __port_reset(pdev); + afu_dma_region_destroy(pdata); + mutex_unlock(&pdata->lock); + dfl_feature_dev_use_end(pdata); return 0; @@ -364,6 +368,55 @@ static long afu_ioctl_get_region_info(struct dfl_feature_platform_data *pdata, 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; @@ -384,6 +437,10 @@ static long afu_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 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 @@ -460,6 +517,7 @@ static int afu_dev_init(struct platform_device *pdev) 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; @@ -473,6 +531,7 @@ static int afu_dev_destroy(struct platform_device *pdev) 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); diff --git a/drivers/fpga/dfl-afu.h b/drivers/fpga/dfl-afu.h index 11ce2cf99759..0c7630ae3cda 100644 --- a/drivers/fpga/dfl-afu.h +++ b/drivers/fpga/dfl-afu.h @@ -40,12 +40,32 @@ struct dfl_afu_mmio_region { 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. */ @@ -54,6 +74,7 @@ struct dfl_afu { int num_regions; u8 num_umsgs; struct list_head regions; + struct rb_root dma_regions; struct dfl_feature_platform_data *pdata; }; @@ -68,4 +89,12 @@ int afu_mmio_region_get_by_index(struct dfl_feature_platform_data *pdata, int afu_mmio_region_get_by_offset(struct dfl_feature_platform_data *pdata, u64 offset, u64 size, struct dfl_afu_mmio_region *pregion); -#endif +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/include/uapi/linux/fpga-dfl.h b/include/uapi/linux/fpga-dfl.h index a3ccdfb115a5..2e324e515c41 100644 --- a/include/uapi/linux/fpga-dfl.h +++ b/include/uapi/linux/fpga-dfl.h @@ -114,6 +114,43 @@ struct dfl_fpga_port_region_info { #define DFL_FPGA_PORT_GET_REGION_INFO _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 2) +/** + * DFL_FPGA_PORT_DMA_MAP - _IOWR(DFL_FPGA_MAGIC, DFL_PORT_BASE + 3, + * struct dfl_fpga_port_dma_map) + * + * Map the dma memory per user_addr and length which are provided by caller. + * Driver fills the iova in provided struct afu_port_dma_map. + * This interface only accepts page-size aligned user memory for dma mapping. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_dma_map { + /* Input */ + __u32 argsz; /* Structure length */ + __u32 flags; /* Zero for now */ + __u64 user_addr; /* Process virtual address */ + __u64 length; /* Length of mapping (bytes)*/ + /* Output */ + __u64 iova; /* IO virtual address */ +}; + +#define DFL_FPGA_PORT_DMA_MAP _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 3) + +/** + * DFL_FPGA_PORT_DMA_UNMAP - _IOW(FPGA_MAGIC, PORT_BASE + 4, + * struct dfl_fpga_port_dma_unmap) + * + * Unmap the dma memory per iova provided by caller. + * Return: 0 on success, -errno on failure. + */ +struct dfl_fpga_port_dma_unmap { + /* Input */ + __u32 argsz; /* Structure length */ + __u32 flags; /* Zero for now */ + __u64 iova; /* IO virtual address */ +}; + +#define DFL_FPGA_PORT_DMA_UNMAP _IO(DFL_FPGA_MAGIC, DFL_PORT_BASE + 4) + /* IOCTLs for FME file descriptor */ /** -- cgit v1.2.3-71-gd317 From 45cd74cb5061781e793a098c420a7f548fdc9e7d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 10 Jul 2018 17:15:38 +0200 Subject: eventpoll.h: wrap casts in () properly When importing the latest copy of the kernel headers into Bionic, Christpher and Elliott noticed that the eventpoll.h casts were not wrapped in (). As it is, clang complains about macros without surrounding (), so this makes it a pain for userspace tools. So fix it up by adding another () pair, and make them line up purty by using tabs. Fixes: 65aaf87b3aa2 ("add EPOLLNVAL, annotate EPOLL... and event_poll->event") Reported-by: Christopher Ferris Reported-by: Elliott Hughes Cc: stable Cc: Thomas Gleixner Cc: Al Viro Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/eventpoll.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/eventpoll.h b/include/uapi/linux/eventpoll.h index bf48e71f2634..8a3432d0f0dc 100644 --- a/include/uapi/linux/eventpoll.h +++ b/include/uapi/linux/eventpoll.h @@ -42,7 +42,7 @@ #define EPOLLRDHUP (__force __poll_t)0x00002000 /* Set exclusive wakeup mode for the target file descriptor */ -#define EPOLLEXCLUSIVE (__force __poll_t)(1U << 28) +#define EPOLLEXCLUSIVE ((__force __poll_t)(1U << 28)) /* * Request the handling of system wakeup events so as to prevent system suspends @@ -54,13 +54,13 @@ * * Requires CAP_BLOCK_SUSPEND */ -#define EPOLLWAKEUP (__force __poll_t)(1U << 29) +#define EPOLLWAKEUP ((__force __poll_t)(1U << 29)) /* Set the One Shot behaviour for the target file descriptor */ -#define EPOLLONESHOT (__force __poll_t)(1U << 30) +#define EPOLLONESHOT ((__force __poll_t)(1U << 30)) /* Set the Edge Triggered behaviour for the target file descriptor */ -#define EPOLLET (__force __poll_t)(1U << 31) +#define EPOLLET ((__force __poll_t)(1U << 31)) /* * On x86-64 make the 64bit structure have the same alignment as the -- cgit v1.2.3-71-gd317