![Alan Tull](/assets/img/avatar_default.png)
Changes to fpga_region_register function to not set drvdata. Setting drvdata is fine for DT based devices that will have one region per platform device. However PCIe based devices may have multiple FPGA regions under one PCIe device. Without these changes, the PCIe solution has to create an extra device for each child region to hold drvdata. Signed-off-by: Alan Tull <atull@kernel.org> Reported-by: Jiuyue Ma <majiuyue@huawei.com> Signed-off-by: Moritz Fischer <mdf@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
245 lines
5.9 KiB
C
245 lines
5.9 KiB
C
/*
|
|
* FPGA Region - Device Tree support for FPGA programming under Linux
|
|
*
|
|
* Copyright (C) 2013-2016 Altera Corporation
|
|
* Copyright (C) 2017 Intel 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/fpga/fpga-bridge.h>
|
|
#include <linux/fpga/fpga-mgr.h>
|
|
#include <linux/fpga/fpga-region.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
static DEFINE_IDA(fpga_region_ida);
|
|
static struct class *fpga_region_class;
|
|
|
|
struct fpga_region *fpga_region_class_find(
|
|
struct device *start, const void *data,
|
|
int (*match)(struct device *, const void *))
|
|
{
|
|
struct device *dev;
|
|
|
|
dev = class_find_device(fpga_region_class, start, data, match);
|
|
if (!dev)
|
|
return NULL;
|
|
|
|
return to_fpga_region(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(fpga_region_class_find);
|
|
|
|
/**
|
|
* fpga_region_get - get an exclusive reference to a fpga region
|
|
* @region: FPGA Region struct
|
|
*
|
|
* Caller should call fpga_region_put() when done with region.
|
|
*
|
|
* Return fpga_region struct if successful.
|
|
* Return -EBUSY if someone already has a reference to the region.
|
|
* Return -ENODEV if @np is not a FPGA Region.
|
|
*/
|
|
static struct fpga_region *fpga_region_get(struct fpga_region *region)
|
|
{
|
|
struct device *dev = ®ion->dev;
|
|
|
|
if (!mutex_trylock(®ion->mutex)) {
|
|
dev_dbg(dev, "%s: FPGA Region already in use\n", __func__);
|
|
return ERR_PTR(-EBUSY);
|
|
}
|
|
|
|
get_device(dev);
|
|
if (!try_module_get(dev->parent->driver->owner)) {
|
|
put_device(dev);
|
|
mutex_unlock(®ion->mutex);
|
|
return ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
dev_dbg(dev, "get\n");
|
|
|
|
return region;
|
|
}
|
|
|
|
/**
|
|
* fpga_region_put - release a reference to a region
|
|
*
|
|
* @region: FPGA region
|
|
*/
|
|
static void fpga_region_put(struct fpga_region *region)
|
|
{
|
|
struct device *dev = ®ion->dev;
|
|
|
|
dev_dbg(dev, "put\n");
|
|
|
|
module_put(dev->parent->driver->owner);
|
|
put_device(dev);
|
|
mutex_unlock(®ion->mutex);
|
|
}
|
|
|
|
/**
|
|
* fpga_region_program_fpga - program FPGA
|
|
* @region: FPGA region
|
|
* Program an FPGA using fpga image info (region->info).
|
|
* If the region has a get_bridges function, the exclusive reference for the
|
|
* bridges will be held if programming succeeds. This is intended to prevent
|
|
* reprogramming the region until the caller considers it safe to do so.
|
|
* The caller will need to call fpga_bridges_put() before attempting to
|
|
* reprogram the region.
|
|
* Return 0 for success or negative error code.
|
|
*/
|
|
int fpga_region_program_fpga(struct fpga_region *region)
|
|
{
|
|
struct device *dev = ®ion->dev;
|
|
struct fpga_image_info *info = region->info;
|
|
int ret;
|
|
|
|
region = fpga_region_get(region);
|
|
if (IS_ERR(region)) {
|
|
dev_err(dev, "failed to get FPGA region\n");
|
|
return PTR_ERR(region);
|
|
}
|
|
|
|
ret = fpga_mgr_lock(region->mgr);
|
|
if (ret) {
|
|
dev_err(dev, "FPGA manager is busy\n");
|
|
goto err_put_region;
|
|
}
|
|
|
|
/*
|
|
* In some cases, we already have a list of bridges in the
|
|
* fpga region struct. Or we don't have any bridges.
|
|
*/
|
|
if (region->get_bridges) {
|
|
ret = region->get_bridges(region);
|
|
if (ret) {
|
|
dev_err(dev, "failed to get fpga region bridges\n");
|
|
goto err_unlock_mgr;
|
|
}
|
|
}
|
|
|
|
ret = fpga_bridges_disable(®ion->bridge_list);
|
|
if (ret) {
|
|
dev_err(dev, "failed to disable bridges\n");
|
|
goto err_put_br;
|
|
}
|
|
|
|
ret = fpga_mgr_load(region->mgr, info);
|
|
if (ret) {
|
|
dev_err(dev, "failed to load FPGA image\n");
|
|
goto err_put_br;
|
|
}
|
|
|
|
ret = fpga_bridges_enable(®ion->bridge_list);
|
|
if (ret) {
|
|
dev_err(dev, "failed to enable region bridges\n");
|
|
goto err_put_br;
|
|
}
|
|
|
|
fpga_mgr_unlock(region->mgr);
|
|
fpga_region_put(region);
|
|
|
|
return 0;
|
|
|
|
err_put_br:
|
|
if (region->get_bridges)
|
|
fpga_bridges_put(®ion->bridge_list);
|
|
err_unlock_mgr:
|
|
fpga_mgr_unlock(region->mgr);
|
|
err_put_region:
|
|
fpga_region_put(region);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fpga_region_program_fpga);
|
|
|
|
int fpga_region_register(struct device *dev, struct fpga_region *region)
|
|
{
|
|
int id, ret = 0;
|
|
|
|
id = ida_simple_get(&fpga_region_ida, 0, 0, GFP_KERNEL);
|
|
if (id < 0)
|
|
return id;
|
|
|
|
mutex_init(®ion->mutex);
|
|
INIT_LIST_HEAD(®ion->bridge_list);
|
|
device_initialize(®ion->dev);
|
|
region->dev.groups = region->groups;
|
|
region->dev.class = fpga_region_class;
|
|
region->dev.parent = dev;
|
|
region->dev.of_node = dev->of_node;
|
|
region->dev.id = id;
|
|
|
|
ret = dev_set_name(®ion->dev, "region%d", id);
|
|
if (ret)
|
|
goto err_remove;
|
|
|
|
ret = device_add(®ion->dev);
|
|
if (ret)
|
|
goto err_remove;
|
|
|
|
return 0;
|
|
|
|
err_remove:
|
|
ida_simple_remove(&fpga_region_ida, id);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fpga_region_register);
|
|
|
|
int fpga_region_unregister(struct fpga_region *region)
|
|
{
|
|
device_unregister(®ion->dev);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fpga_region_unregister);
|
|
|
|
static void fpga_region_dev_release(struct device *dev)
|
|
{
|
|
struct fpga_region *region = to_fpga_region(dev);
|
|
|
|
ida_simple_remove(&fpga_region_ida, region->dev.id);
|
|
}
|
|
|
|
/**
|
|
* fpga_region_init - init function for fpga_region class
|
|
* Creates the fpga_region class and registers a reconfig notifier.
|
|
*/
|
|
static int __init fpga_region_init(void)
|
|
{
|
|
fpga_region_class = class_create(THIS_MODULE, "fpga_region");
|
|
if (IS_ERR(fpga_region_class))
|
|
return PTR_ERR(fpga_region_class);
|
|
|
|
fpga_region_class->dev_release = fpga_region_dev_release;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit fpga_region_exit(void)
|
|
{
|
|
class_destroy(fpga_region_class);
|
|
ida_destroy(&fpga_region_ida);
|
|
}
|
|
|
|
subsys_initcall(fpga_region_init);
|
|
module_exit(fpga_region_exit);
|
|
|
|
MODULE_DESCRIPTION("FPGA Region");
|
|
MODULE_AUTHOR("Alan Tull <atull@kernel.org>");
|
|
MODULE_LICENSE("GPL v2");
|