From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Alan Tull Subject: [RFC PATCH 1/3] fpga manager framework core Date: Thu, 31 Jul 2014 16:59:02 -0500 Message-ID: <1406843944-7780-2-git-send-email-atull@opensource.altera.com> In-Reply-To: <1406843944-7780-1-git-send-email-atull@opensource.altera.com> References: <1406843944-7780-1-git-send-email-atull@opensource.altera.com> MIME-Version: 1.0 Content-Type: text/plain To: linux-kernel@vger.kernel.org Cc: "H. Peter Anvin" , Greg Kroah-Hartman , Jason Gunthorpe , Pantelis Antoniou , Grant Likely , Rob Herring , devicetree@vger.kernel.org, Pavel Machek , Mark Brown , Philip Balister , Alessandro Rubini , Steffen Trumtrar , Jason Cooper , Kyle Teske , Nicolas Pitre , Felipe Balbi , Mauro Carvalho Chehab , David Brown , Rob Landley , "David S. Miller" , Joe Perches , Cesar Eduardo Barros , Samuel Ortiz , Andrew Morton , Michal Simek , Michal Simek , Alan Tull , Dinh Nguyen , Yves Vandervennet , Alan Tull List-ID: This core exports methods of doing operations on FPGAs. EXPORT_SYMBOL_GPL(fpga_mgr_write); Write FPGA given a buffer and count. EXPORT_SYMBOL_GPL(fpga_mgr_firmware_write); Request firmware and write that to a fpga EXPORT_SYMBOL_GPL(fpga_mgr_status_get); Get a status string, including failure information EXPORT_SYMBOL_GPL(fpga_mgr_name); Get name of FPGA manager EXPORT_SYMBOL_GPL(register_fpga_manager); EXPORT_SYMBOL_GPL(remove_fpga_manager); Register/unregister low level fpga driver All userspace interfaces are in separate files so that they can be compiled out on production builds where appropriate. Signed-off-by: Alan Tull --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/fpga/Kconfig | 13 ++ drivers/fpga/Makefile | 10 ++ drivers/fpga/fpga-mgr.c | 403 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/fpga-mgr.h | 121 ++++++++++++++ 6 files changed, 550 insertions(+) create mode 100644 drivers/fpga/Kconfig create mode 100644 drivers/fpga/Makefile create mode 100644 drivers/fpga/fpga-mgr.c create mode 100644 include/linux/fpga-mgr.h diff --git a/drivers/Kconfig b/drivers/Kconfig index 0e87a34..b0cbbae 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -34,6 +34,8 @@ source "drivers/message/fusion/Kconfig" source "drivers/firewire/Kconfig" +source "drivers/fpga/Kconfig" + source "drivers/message/i2o/Kconfig" source "drivers/macintosh/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index f98b50d..afdd2aa 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_RESET_CONTROLLER) += reset/ # default. obj-y += tty/ obj-y += char/ +obj-$(CONFIG_FPGA) += fpga/ # gpu/ comes after char for AGP vs DRM startup obj-y += gpu/ diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig new file mode 100644 index 0000000..49293a3 --- /dev/null +++ b/drivers/fpga/Kconfig @@ -0,0 +1,13 @@ +# +# FPGA framework configuration +# + +menu "FPGA devices" + +config FPGA + tristate "FPGA Framework" + help + Say Y here if you want support for configuring FPGAs from the + kernel. The FPGA framework adds a FPGA manager class and FPGA + manager drivers. +endmenu diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile new file mode 100644 index 0000000..c8a676f --- /dev/null +++ b/drivers/fpga/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the fpga framework and fpga manager drivers. +# + +fpga-mgr-core-y += fpga-mgr.o + +# Core FPGA Manager Framework +obj-$(CONFIG_FPGA) += fpga-mgr-core.o + +# FPGA Manager Drivers diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c new file mode 100644 index 0000000..0e2eba4 --- /dev/null +++ b/drivers/fpga/fpga-mgr.c @@ -0,0 +1,403 @@ +/* + * FPGA Manager Core + * + * Copyright (C) 2013-2014 Altera Corporation + * + * With code from the mailing list: + * Copyright (C) 2013 Xilinx, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_IDA(fpga_mgr_ida); +static int fpga_mgr_major; +static struct class *fpga_mgr_class; + +#define FPGA_MAX_MINORS 256 + +static DEFINE_MUTEX(fpga_manager_mutex); +static LIST_HEAD(fpga_manager_list); + +/* + * Unlocked version of fpga_mgr_write function. + * Does not touch flags. So caller must grab the FPGA_MGR_BUSY bit + * and update the FPGA_MGR_FAIL bit. + */ +static int __fpga_mgr_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + int ret; + + if (mgr->mops->write_init) { + mgr->state = FPGA_MGR_WRITE_INIT; + ret = mgr->mops->write_init(mgr); + if (ret) + return ret; + } + + mgr->state = FPGA_MGR_WRITE; + ret = mgr->mops->write(mgr, buf, count); + if (ret) + return ret; + + if (mgr->mops->write_complete) { + mgr->state = FPGA_MGR_WRITE_COMPLETE; + ret = mgr->mops->write_complete(mgr); + if (ret) + return ret; + } + + mgr->state = FPGA_MGR_WRITE_SUCCESS; + + return 0; +} + +int fpga_mgr_write(struct fpga_manager *mgr, const char *buf, size_t count) +{ + int ret; + + if (test_and_set_bit_lock(FPGA_MGR_BUSY, &mgr->flags)) + return -EBUSY; + + dev_info(mgr->dev, "writing buffer to %s\n", mgr->name); + + ret = __fpga_mgr_write(mgr, buf, count); + if (ret) + set_bit(FPGA_MGR_FAIL, &mgr->flags); + else + clear_bit(FPGA_MGR_FAIL, &mgr->flags); + + clear_bit_unlock(FPGA_MGR_BUSY, &mgr->flags); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_mgr_write); + +/* + * Grab lock, request firmware, and write out to the FPGA. + * Update the state before each step to provide info on what step + * failed if there is a failure. + */ +int fpga_mgr_firmware_write(struct fpga_manager *mgr, const char *path) +{ + const struct firmware *fw; + int ret; + + if (test_and_set_bit_lock(FPGA_MGR_BUSY, &mgr->flags)) + return -EBUSY; + + dev_info(mgr->dev, "writing %s to %s\n", path, mgr->name); + + mgr->state = FPGA_MGR_FIRMWARE_REQ; + ret = request_firmware(&fw, path, mgr->dev); + if (ret) + goto fw_write_fail; + + ret = __fpga_mgr_write(mgr, fw->data, fw->size); + if (ret) + goto fw_write_fail; + + clear_bit(FPGA_MGR_FAIL, &mgr->flags); + clear_bit_unlock(FPGA_MGR_BUSY, &mgr->flags); + + return 0; + +fw_write_fail: + set_bit(FPGA_MGR_FAIL, &mgr->flags); + clear_bit_unlock(FPGA_MGR_BUSY, &mgr->flags); + + return ret; +} +EXPORT_SYMBOL_GPL(fpga_mgr_firmware_write); + +int fpga_mgr_status_get(struct fpga_manager *mgr, char *buf) +{ + if (!mgr || !mgr->mops || !mgr->mops->status) + return -ENODEV; + + return mgr->mops->status(mgr, buf); +} +EXPORT_SYMBOL_GPL(fpga_mgr_status_get); + +int fpga_mgr_name(struct fpga_manager *mgr, char *buf) +{ + if (!mgr) + return -ENODEV; + + return sprintf(buf, "%s\n", mgr->name); +} +EXPORT_SYMBOL_GPL(fpga_mgr_name); + +static int fpga_mgr_get_new_minor(struct fpga_manager *mgr, int request_nr) +{ + int nr, start; + + /* check specified minor number */ + if (request_nr >= FPGA_MAX_MINORS) { + dev_err(mgr->parent, "Out of device minors (%d)\n", request_nr); + return -ENODEV; + } + + /* + * If request_nr == -1, dynamically allocate number. + * If request_nr >= 0, attempt to get specific number. + */ + if (request_nr == -1) + start = 0; + else + start = request_nr; + + nr = ida_simple_get(&fpga_mgr_ida, start, FPGA_MAX_MINORS, GFP_KERNEL); + + /* return error code */ + if (nr < 0) + return nr; + + if ((request_nr != -1) && (request_nr != nr)) { + dev_err(mgr->parent, + "Could not get requested device minor (%d)\n", nr); + ida_simple_remove(&fpga_mgr_ida, nr); + return -ENODEV; + } + + mgr->nr = nr; + + return 0; +} + +static void fpga_mgr_free_minor(int nr) +{ + ida_simple_remove(&fpga_mgr_ida, nr); +} + +const char *state_str[] = { + "default", + "firmware_request", + "write_init", + "write", + "write_complete", + "write_success", + "read_init", + "read", + "read_complete", + "read_done", +}; + +/* + * Provide status as: [state] [fail] [busy] such as + * 'firmware_request fail' = failed to load firmware image from filesystem + * 'write fail' = failed while writing to FPGA + * 'write_success' = after writing, low level driver returns success + */ +static int fpga_mgr_ops_framework_status(struct fpga_manager *mgr, char *buf) +{ + int ret = 0, flags = mgr->flags; + + ret += sprintf(buf + ret, state_str[mgr->state]); + + if (flags & BIT(FPGA_MGR_FAIL)) + ret += sprintf(buf + ret, " fail"); + + if (flags & BIT(FPGA_MGR_BUSY)) + ret += sprintf(buf + ret, " busy"); + + ret += sprintf(buf + ret, "\n"); + + return ret; +} + +static int fpga_mgr_suspend(struct device *dev) +{ + struct fpga_manager *mgr = dev_get_drvdata(dev); + + if (!mgr) + return -ENODEV; + + if (mgr->mops->suspend) + return mgr->mops->suspend(mgr); + + return 0; +} + +static int fpga_mgr_resume(struct device *dev) +{ + struct fpga_manager *mgr = dev_get_drvdata(dev); + int ret = 0; + + if (!mgr) + return -ENODEV; + + if (mgr->mops->resume) { + ret = mgr->mops->resume(mgr); + if (ret) + return ret; + } + + return 0; +} + +const struct dev_pm_ops fpga_mgr_dev_pm_ops = { + .suspend = fpga_mgr_suspend, + .resume = fpga_mgr_resume, +}; + +int register_fpga_manager(struct platform_device *pdev, + struct fpga_manager_ops *mops, + const char *name, + unsigned int num_areas, + void *priv) +{ + struct fpga_manager *mgr; + int ret; + + BUG_ON(!mops || !name || !strlen(name)); + + mgr = devm_kzalloc(&pdev->dev, sizeof(struct fpga_manager), GFP_KERNEL); + if (!mgr) + return -ENOMEM; + + platform_set_drvdata(pdev, mgr); + mgr->mops = mops; + + /* implementing status() for each fpga manager is optional */ + if (!mgr->mops->status) + mgr->mops->status = fpga_mgr_ops_framework_status; + + mgr->np = pdev->dev.of_node; + mgr->parent = get_device(&pdev->dev); + mgr->priv = priv; + mgr->name = name; + mgr->num_areas = num_areas; + init_completion(&mgr->status_complete); + + ret = fpga_mgr_get_new_minor(mgr, pdev->id); + if (ret) + goto error_kfree; + + if (mops->isr) { + mgr->irq = irq_of_parse_and_map(mgr->np, 0); + if (mgr->irq == NO_IRQ) { + dev_err(mgr->parent, "failed to map interrupt\n"); + goto error_irq_map; + } + + ret = request_irq(mgr->irq, mops->isr, 0, "fpga-mgr", mgr); + if (ret < 0) { + dev_err(mgr->parent, "error requesting interrupt\n"); + goto error_irq_req; + } + } + + mgr->dev = device_create(fpga_mgr_class, mgr->parent, MKDEV(0, 0), mgr, + "fpga%d", mgr->nr); + if (IS_ERR(mgr->dev)) { + ret = PTR_ERR(mgr->dev); + goto error_device; + } + + fpga_mgr_class->pm = &fpga_mgr_dev_pm_ops; + + dev_info(mgr->parent, "fpga manager [%s] registered as minor %d\n", + mgr->name, mgr->nr); + + INIT_LIST_HEAD(&mgr->list); + mutex_lock(&fpga_manager_mutex); + list_add(&mgr->list, &fpga_manager_list); + mutex_unlock(&fpga_manager_mutex); + + return 0; + +error_device: + cdev_del(&mgr->cdev); + free_irq(mgr->irq, mgr); +error_irq_req: + irq_dispose_mapping(mgr->irq); +error_irq_map: + fpga_mgr_free_minor(mgr->nr); +error_kfree: + put_device(mgr->parent); + return ret; +} +EXPORT_SYMBOL_GPL(register_fpga_manager); + +void remove_fpga_manager(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + + if (!mgr) + return; + + if (mgr->mops->fpga_remove) + mgr->mops->fpga_remove(mgr); + + mutex_lock(&fpga_manager_mutex); + list_del(&mgr->list); + mutex_unlock(&fpga_manager_mutex); + + device_destroy(fpga_mgr_class, MKDEV(fpga_mgr_major, mgr->nr)); + cdev_del(&mgr->cdev); + free_irq(mgr->irq, mgr); + irq_dispose_mapping(mgr->irq); + fpga_mgr_free_minor(mgr->nr); + put_device(mgr->parent); +} +EXPORT_SYMBOL_GPL(remove_fpga_manager); + +static int __init fpga_mgr_dev_init(void) +{ + dev_t fpga_mgr_dev; + int ret; + + pr_info("FPGA Manager framework driver\n"); + + fpga_mgr_class = class_create(THIS_MODULE, "fpga_manager"); + if (IS_ERR(fpga_mgr_class)) + return PTR_ERR(fpga_mgr_class); + + ret = alloc_chrdev_region(&fpga_mgr_dev, 0, FPGA_MAX_MINORS, + "fpga_manager"); + if (ret) { + class_destroy(fpga_mgr_class); + return ret; + } + + fpga_mgr_major = MAJOR(fpga_mgr_dev); + + return 0; +} + +static void __exit fpga_mgr_dev_exit(void) +{ + unregister_chrdev_region(MKDEV(fpga_mgr_major, 0), FPGA_MAX_MINORS); + class_destroy(fpga_mgr_class); + ida_destroy(&fpga_mgr_ida); +} + +MODULE_AUTHOR("Alan Tull "); +MODULE_DESCRIPTION("FPGA Manager framework driver"); +MODULE_LICENSE("GPL v2"); + +subsys_initcall(fpga_mgr_dev_init); +module_exit(fpga_mgr_dev_exit); diff --git a/include/linux/fpga-mgr.h b/include/linux/fpga-mgr.h new file mode 100644 index 0000000..6af0873 --- /dev/null +++ b/include/linux/fpga-mgr.h @@ -0,0 +1,121 @@ +/* + * FPGA Framework + * + * Copyright (C) 2013-2014 Altera Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include + +#ifndef _LINUX_FPGA_MGR_H +#define _LINUX_FPGA_MGR_H + +struct fpga_manager; + +/* + * fpga_manager_ops are the low level functions implemented by a specific + * fpga manager driver. Leaving any of these out that aren't needed is fine + * as they are all tested for NULL before being called. + */ +struct fpga_manager_ops { + /* Returns a string of the FPGA's status */ + int (*status)(struct fpga_manager *mgr, char *buf); + + /* Prepare the FPGA for reading its confuration data */ + int (*read_init)(struct fpga_manager *mgr); + + /* Read count bytes configuration data from the FPGA */ + ssize_t (*read)(struct fpga_manager *mgr, char *buf, size_t count); + + /* Return FPGA to a default state after reading is done */ + int (*read_complete)(struct fpga_manager *mgr); + + /* Prepare the FPGA to receive confuration data */ + int (*write_init)(struct fpga_manager *mgr); + + /* Write count bytes of configuration data to the FPGA */ + int (*write)(struct fpga_manager *mgr, const char *buf, size_t count); + + /* Return FPGA to default state after writing is done */ + int (*write_complete)(struct fpga_manager *mgr); + + /* Optional: Set FPGA into a specific state during driver remove */ + void (*fpga_remove)(struct fpga_manager *mgr); + + int (*suspend)(struct fpga_manager *mgr); + + int (*resume)(struct fpga_manager *mgr); + + /* FPGA mangager isr */ + irqreturn_t (*isr)(int irq, void *dev_id); +}; + +/* flag bits */ +#define FPGA_MGR_BUSY 0 +#define FPGA_MGR_FAIL 1 + +/* States */ +enum fpga_mgr_states { + FPGA_MGR_DEFAULT, + FPGA_MGR_FIRMWARE_REQ, + FPGA_MGR_WRITE_INIT, + FPGA_MGR_WRITE, + FPGA_MGR_WRITE_COMPLETE, + FPGA_MGR_WRITE_SUCCESS, + FPGA_MGR_READ_INIT, + FPGA_MGR_READ, + FPGA_MGR_READ_COMPLETE, + FPGA_MGR_READ_DONE, +}; + +struct fpga_manager { + const char *name; + int nr; + struct device_node *np; + struct device *parent; + struct device *dev; + struct cdev cdev; + struct configfs_subsystem configfs; + + struct list_head list; + + int irq; + struct completion status_complete; + unsigned long flags; + enum fpga_mgr_states state; + struct fpga_manager_ops *mops; + void *priv; +}; + +#if IS_ENABLED(CONFIG_FPGA) + +int fpga_mgr_firmware_write(struct fpga_manager *mgr, const char *path); +int fpga_mgr_write(struct fpga_manager *mgr, const char *buf, size_t count); +int fpga_mgr_status_get(struct fpga_manager *mgr, char *buf); +int fpga_mgr_name(struct fpga_manager *mgr, char *buf); + +int register_fpga_manager(struct platform_device *pdev, + struct fpga_manager_ops *mops, + const char *name, + void *priv); + +void remove_fpga_manager(struct platform_device *pdev); + +#endif /* CONFIG_FPGA */ +#endif /*_LINUX_FPGA_MGR_H */ -- 1.7.9.5