323 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * FPGA Region - Support for FPGA programming under Linux4 *5 * Copyright (C) 2013-2016 Altera Corporation6 * Copyright (C) 2017 Intel Corporation7 */8#include <linux/fpga/fpga-bridge.h>9#include <linux/fpga/fpga-mgr.h>10#include <linux/fpga/fpga-region.h>11#include <linux/idr.h>12#include <linux/kernel.h>13#include <linux/list.h>14#include <linux/module.h>15#include <linux/slab.h>16#include <linux/spinlock.h>17 18static DEFINE_IDA(fpga_region_ida);19static const struct class fpga_region_class;20 21struct fpga_region *22fpga_region_class_find(struct device *start, const void *data,23 int (*match)(struct device *, const void *))24{25 struct device *dev;26 27 dev = class_find_device(&fpga_region_class, start, data, match);28 if (!dev)29 return NULL;30 31 return to_fpga_region(dev);32}33EXPORT_SYMBOL_GPL(fpga_region_class_find);34 35/**36 * fpga_region_get - get an exclusive reference to an fpga region37 * @region: FPGA Region struct38 *39 * Caller should call fpga_region_put() when done with region.40 *41 * Return:42 * * fpga_region struct if successful.43 * * -EBUSY if someone already has a reference to the region.44 * * -ENODEV if can't take parent driver module refcount.45 */46static struct fpga_region *fpga_region_get(struct fpga_region *region)47{48 struct device *dev = ®ion->dev;49 50 if (!mutex_trylock(®ion->mutex)) {51 dev_dbg(dev, "%s: FPGA Region already in use\n", __func__);52 return ERR_PTR(-EBUSY);53 }54 55 get_device(dev);56 if (!try_module_get(region->ops_owner)) {57 put_device(dev);58 mutex_unlock(®ion->mutex);59 return ERR_PTR(-ENODEV);60 }61 62 dev_dbg(dev, "get\n");63 64 return region;65}66 67/**68 * fpga_region_put - release a reference to a region69 *70 * @region: FPGA region71 */72static void fpga_region_put(struct fpga_region *region)73{74 struct device *dev = ®ion->dev;75 76 dev_dbg(dev, "put\n");77 78 module_put(region->ops_owner);79 put_device(dev);80 mutex_unlock(®ion->mutex);81}82 83/**84 * fpga_region_program_fpga - program FPGA85 *86 * @region: FPGA region87 *88 * Program an FPGA using fpga image info (region->info).89 * If the region has a get_bridges function, the exclusive reference for the90 * bridges will be held if programming succeeds. This is intended to prevent91 * reprogramming the region until the caller considers it safe to do so.92 * The caller will need to call fpga_bridges_put() before attempting to93 * reprogram the region.94 *95 * Return: 0 for success or negative error code.96 */97int fpga_region_program_fpga(struct fpga_region *region)98{99 struct device *dev = ®ion->dev;100 struct fpga_image_info *info = region->info;101 int ret;102 103 region = fpga_region_get(region);104 if (IS_ERR(region)) {105 dev_err(dev, "failed to get FPGA region\n");106 return PTR_ERR(region);107 }108 109 ret = fpga_mgr_lock(region->mgr);110 if (ret) {111 dev_err(dev, "FPGA manager is busy\n");112 goto err_put_region;113 }114 115 /*116 * In some cases, we already have a list of bridges in the117 * fpga region struct. Or we don't have any bridges.118 */119 if (region->get_bridges) {120 ret = region->get_bridges(region);121 if (ret) {122 dev_err(dev, "failed to get fpga region bridges\n");123 goto err_unlock_mgr;124 }125 }126 127 ret = fpga_bridges_disable(®ion->bridge_list);128 if (ret) {129 dev_err(dev, "failed to disable bridges\n");130 goto err_put_br;131 }132 133 ret = fpga_mgr_load(region->mgr, info);134 if (ret) {135 dev_err(dev, "failed to load FPGA image\n");136 goto err_put_br;137 }138 139 ret = fpga_bridges_enable(®ion->bridge_list);140 if (ret) {141 dev_err(dev, "failed to enable region bridges\n");142 goto err_put_br;143 }144 145 fpga_mgr_unlock(region->mgr);146 fpga_region_put(region);147 148 return 0;149 150err_put_br:151 if (region->get_bridges)152 fpga_bridges_put(®ion->bridge_list);153err_unlock_mgr:154 fpga_mgr_unlock(region->mgr);155err_put_region:156 fpga_region_put(region);157 158 return ret;159}160EXPORT_SYMBOL_GPL(fpga_region_program_fpga);161 162static ssize_t compat_id_show(struct device *dev,163 struct device_attribute *attr, char *buf)164{165 struct fpga_region *region = to_fpga_region(dev);166 167 if (!region->compat_id)168 return -ENOENT;169 170 return sprintf(buf, "%016llx%016llx\n",171 (unsigned long long)region->compat_id->id_h,172 (unsigned long long)region->compat_id->id_l);173}174 175static DEVICE_ATTR_RO(compat_id);176 177static struct attribute *fpga_region_attrs[] = {178 &dev_attr_compat_id.attr,179 NULL,180};181ATTRIBUTE_GROUPS(fpga_region);182 183/**184 * __fpga_region_register_full - create and register an FPGA Region device185 * @parent: device parent186 * @info: parameters for FPGA Region187 * @owner: module containing the get_bridges function188 *189 * Return: struct fpga_region or ERR_PTR()190 */191struct fpga_region *192__fpga_region_register_full(struct device *parent, const struct fpga_region_info *info,193 struct module *owner)194{195 struct fpga_region *region;196 int id, ret = 0;197 198 if (!info) {199 dev_err(parent,200 "Attempt to register without required info structure\n");201 return ERR_PTR(-EINVAL);202 }203 204 region = kzalloc(sizeof(*region), GFP_KERNEL);205 if (!region)206 return ERR_PTR(-ENOMEM);207 208 id = ida_alloc(&fpga_region_ida, GFP_KERNEL);209 if (id < 0) {210 ret = id;211 goto err_free;212 }213 214 region->mgr = info->mgr;215 region->compat_id = info->compat_id;216 region->priv = info->priv;217 region->get_bridges = info->get_bridges;218 region->ops_owner = owner;219 220 mutex_init(®ion->mutex);221 INIT_LIST_HEAD(®ion->bridge_list);222 223 region->dev.class = &fpga_region_class;224 region->dev.parent = parent;225 region->dev.of_node = parent->of_node;226 region->dev.id = id;227 228 ret = dev_set_name(®ion->dev, "region%d", id);229 if (ret)230 goto err_remove;231 232 ret = device_register(®ion->dev);233 if (ret) {234 put_device(®ion->dev);235 return ERR_PTR(ret);236 }237 238 return region;239 240err_remove:241 ida_free(&fpga_region_ida, id);242err_free:243 kfree(region);244 245 return ERR_PTR(ret);246}247EXPORT_SYMBOL_GPL(__fpga_region_register_full);248 249/**250 * __fpga_region_register - create and register an FPGA Region device251 * @parent: device parent252 * @mgr: manager that programs this region253 * @get_bridges: optional function to get bridges to a list254 * @owner: module containing the get_bridges function255 *256 * This simple version of the register function should be sufficient for most users.257 * The fpga_region_register_full() function is available for users that need to258 * pass additional, optional parameters.259 *260 * Return: struct fpga_region or ERR_PTR()261 */262struct fpga_region *263__fpga_region_register(struct device *parent, struct fpga_manager *mgr,264 int (*get_bridges)(struct fpga_region *), struct module *owner)265{266 struct fpga_region_info info = { 0 };267 268 info.mgr = mgr;269 info.get_bridges = get_bridges;270 271 return __fpga_region_register_full(parent, &info, owner);272}273EXPORT_SYMBOL_GPL(__fpga_region_register);274 275/**276 * fpga_region_unregister - unregister an FPGA region277 * @region: FPGA region278 *279 * This function is intended for use in an FPGA region driver's remove function.280 */281void fpga_region_unregister(struct fpga_region *region)282{283 device_unregister(®ion->dev);284}285EXPORT_SYMBOL_GPL(fpga_region_unregister);286 287static void fpga_region_dev_release(struct device *dev)288{289 struct fpga_region *region = to_fpga_region(dev);290 291 ida_free(&fpga_region_ida, region->dev.id);292 kfree(region);293}294 295static const struct class fpga_region_class = {296 .name = "fpga_region",297 .dev_groups = fpga_region_groups,298 .dev_release = fpga_region_dev_release,299};300 301/**302 * fpga_region_init - creates the fpga_region class.303 *304 * Return: 0 on success or ERR_PTR() on error.305 */306static int __init fpga_region_init(void)307{308 return class_register(&fpga_region_class);309}310 311static void __exit fpga_region_exit(void)312{313 class_unregister(&fpga_region_class);314 ida_destroy(&fpga_region_ida);315}316 317subsys_initcall(fpga_region_init);318module_exit(fpga_region_exit);319 320MODULE_DESCRIPTION("FPGA Region");321MODULE_AUTHOR("Alan Tull <atull@kernel.org>");322MODULE_LICENSE("GPL v2");323