// SPDX-License-Identifier: GPL-2.0/* * Driver for FPGA Device Feature List (DFL) Support * * Copyright (C) 2017-2018 Intel Corporation, Inc. * * Authors: * Kang Luwei <luwei.kang@intel.com> * Zhang Yi <yi.z.zhang@intel.com> * Wu Hao <hao.wu@intel.com> * Xiao Guangrong <guangrong.xiao@linux.intel.com> */#include <linux/dfl.h>#include <linux/fpga-dfl.h>#include <linux/module.h>#include <linux/overflow.h>#include <linux/uaccess.h> #include "dfl.h" static DEFINE_MUTEX(dfl_id_mutex); /* * when adding a new feature dev support in DFL framework, it's required to * add a new item in enum dfl_id_type and provide related information in below * dfl_devs table which is indexed by dfl_id_type, e.g. name string used for * platform device creation (define name strings in dfl.h, as they could be * reused by platform device drivers). * * if the new feature dev needs chardev support, then it's required to add * a new item in dfl_chardevs table and configure dfl_devs[i].devt_type as * index to dfl_chardevs table. If no chardev support just set devt_type * as one invalid index (DFL_FPGA_DEVT_MAX). */enum dfl_fpga_devt_type { DFL_FPGA_DEVT_FME, DFL_FPGA_DEVT_PORT, DFL_FPGA_DEVT_MAX,}; static struct lock_class_key dfl_pdata_keys[DFL_ID_MAX]; static const char *dfl_pdata_key_strings[DFL_ID_MAX] = { "dfl-fme-pdata", "dfl-port-pdata",}; /** * struct dfl_dev_info - dfl feature device information. * @name: name string of the feature platform device. * @dfh_id: id value in Device Feature Header (DFH) register by DFL spec. * @id: idr id of the feature dev. * @devt_type: index to dfl_chrdevs[]. */struct dfl_dev_info { const char *name; u16 dfh_id; struct idr id; enum dfl_fpga_devt_type devt_type;}; /* it is indexed by dfl_id_type */static struct dfl_dev_info dfl_devs[] = { {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME, .devt_type = DFL_FPGA_DEVT_FME}, {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT, .devt_type = DFL_FPGA_DEVT_PORT},}; /** * struct dfl_chardev_info - chardev information of dfl feature device * @name: nmae string of the char device. * @devt: devt of the char device. */struct dfl_chardev_info { const char *name; dev_t devt;}; /* indexed by enum dfl_fpga_devt_type */static struct dfl_chardev_info dfl_chrdevs[] = { {.name = DFL_FPGA_FEATURE_DEV_FME}, {.name = DFL_FPGA_FEATURE_DEV_PORT},}; static void dfl_ids_init(void){ int i; for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) idr_init(&dfl_devs[i].id);} static void dfl_ids_destroy(void){ int i; for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) idr_destroy(&dfl_devs[i].id);} static int dfl_id_alloc(enum dfl_id_type type, struct device *dev){ int id; WARN_ON(type >= DFL_ID_MAX); mutex_lock(&dfl_id_mutex); id = idr_alloc(&dfl_devs[type].id, dev, 0, 0, GFP_KERNEL); mutex_unlock(&dfl_id_mutex); return id;} static void dfl_id_free(enum dfl_id_type type, int id){ WARN_ON(type >= DFL_ID_MAX); mutex_lock(&dfl_id_mutex); idr_remove(&dfl_devs[type].id, id); mutex_unlock(&dfl_id_mutex);} static enum dfl_id_type dfh_id_to_type(u16 id){ int i; for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) if (dfl_devs[i].dfh_id == id) return i; return DFL_ID_MAX;} /* * introduce a global port_ops list, it allows port drivers to register ops * in such list, then other feature devices (e.g. FME), could use the port * functions even related port platform device is hidden. Below is one example, * in virtualization case of PCIe-based FPGA DFL device, when SRIOV is * enabled, port (and it's AFU) is turned into VF and port platform device * is hidden from system but it's still required to access port to finish FPGA * reconfiguration function in FME. */ static DEFINE_MUTEX(dfl_port_ops_mutex);static LIST_HEAD(dfl_port_ops_list); /** * dfl_fpga_port_ops_get - get matched port ops from the global list * @fdata: feature dev data to match with associated port ops. * Return: matched port ops on success, NULL otherwise. * * Please note that must dfl_fpga_port_ops_put after use the port_ops. */struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct dfl_feature_dev_data *fdata){ struct dfl_fpga_port_ops *ops = NULL; mutex_lock(&dfl_port_ops_mutex); if (list_empty(&dfl_port_ops_list)) goto done; list_for_each_entry(ops, &dfl_port_ops_list, node) { /* match port_ops using the name of platform device */ if (!strcmp(fdata->pdev_name, ops->name)) { if (!try_module_get(ops->owner)) ops = NULL; goto done; } } ops = NULL;done: mutex_unlock(&dfl_port_ops_mutex); return ops;}EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_get); /** * dfl_fpga_port_ops_put - put port ops * @ops: port ops. */void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops){ if (ops && ops->owner) module_put(ops->owner);}EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_put); /** * dfl_fpga_port_ops_add - add port_ops to global list * @ops: port ops to add. */void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops){ mutex_lock(&dfl_port_ops_mutex); list_add_tail(&ops->node, &dfl_port_ops_list); mutex_unlock(&dfl_port_ops_mutex);}EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_add); /** * dfl_fpga_port_ops_del - remove port_ops from global list * @ops: port ops to del. */void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops){ mutex_lock(&dfl_port_ops_mutex); list_del(&ops->node); mutex_unlock(&dfl_port_ops_mutex);}EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_del); /** * dfl_fpga_check_port_id - check the port id * @fdata: port feature dev data. * @pport_id: port id to compare. * * Return: 1 if port device matches with given port id, otherwise 0. */int dfl_fpga_check_port_id(struct dfl_feature_dev_data *fdata, void *pport_id){ struct dfl_fpga_port_ops *port_ops; if (fdata->id != FEATURE_DEV_ID_UNUSED) return fdata->id == *(int *)pport_id; port_ops = dfl_fpga_port_ops_get(fdata); if (!port_ops || !port_ops->get_id) return 0; fdata->id = port_ops->get_id(fdata); dfl_fpga_port_ops_put(port_ops); return fdata->id == *(int *)pport_id;}EXPORT_SYMBOL_GPL(dfl_fpga_check_port_id); static DEFINE_IDA(dfl_device_ida); static const struct dfl_device_id *dfl_match_one_device(const struct dfl_device_id *id, struct dfl_device *ddev){ if (id->type == ddev->type && id->feature_id == ddev->feature_id) return id; return NULL;} static int dfl_bus_match(struct device *dev, const struct device_driver *drv){ struct dfl_device *ddev = to_dfl_dev(dev); const struct dfl_driver *ddrv = to_dfl_drv(drv); const struct dfl_device_id *id_entry; id_entry = ddrv->id_table; if (id_entry) { while (id_entry->feature_id) { if (dfl_match_one_device(id_entry, ddev)) { ddev->id_entry = id_entry; return 1; } id_entry++; } } return 0;} static int dfl_bus_probe(struct device *dev){ struct dfl_driver *ddrv = to_dfl_drv(dev->driver); struct dfl_device *ddev = to_dfl_dev(dev); return ddrv->probe(ddev);} static void dfl_bus_remove(struct device *dev){ struct dfl_driver *ddrv = to_dfl_drv(dev->driver); struct dfl_device *ddev = to_dfl_dev(dev); if (ddrv->remove) ddrv->remove(ddev);} static int dfl_bus_uevent(const struct device *dev, struct kobj_uevent_env *env){ const struct dfl_device *ddev = to_dfl_dev(dev); return add_uevent_var(env, "MODALIAS=dfl:t%04Xf%04X", ddev->type, ddev->feature_id);} static ssize_ttype_show(struct device *dev, struct device_attribute *attr, char *buf){ struct dfl_device *ddev = to_dfl_dev(dev); return sprintf(buf, "0x%x\n", ddev->type);}static DEVICE_ATTR_RO(type); static ssize_tfeature_id_show(struct device *dev, struct device_attribute *attr, char *buf){ struct dfl_device *ddev = to_dfl_dev(dev); return sprintf(buf, "0x%x\n", ddev->feature_id);}static DEVICE_ATTR_RO(feature_id); static struct attribute *dfl_dev_attrs[] = { &dev_attr_type.attr, &dev_attr_feature_id.attr, NULL,};ATTRIBUTE_GROUPS(dfl_dev); static const struct bus_type dfl_bus_type = { .name = "dfl", .match = dfl_bus_match, .probe = dfl_bus_probe, .remove = dfl_bus_remove, .uevent = dfl_bus_uevent, .dev_groups = dfl_dev_groups,}; static void release_dfl_dev(struct device *dev){ struct dfl_device *ddev = to_dfl_dev(dev); if (ddev->mmio_res.parent) release_resource(&ddev->mmio_res); kfree(ddev->params); ida_free(&dfl_device_ida, ddev->id); kfree(ddev->irqs); kfree(ddev);} static struct dfl_device *dfl_dev_add(struct dfl_feature_dev_data *fdata, struct dfl_feature *feature){ struct platform_device *pdev = fdata->dev; struct resource *parent_res; struct dfl_device *ddev; int id, i, ret; ddev = kzalloc_obj(*ddev); if (!ddev) return ERR_PTR(-ENOMEM); id = ida_alloc(&dfl_device_ida, GFP_KERNEL); if (id < 0) { dev_err(&pdev->dev, "unable to get id\n"); kfree(ddev); return ERR_PTR(id); } /* freeing resources by put_device() after device_initialize() */ device_initialize(&ddev->dev); ddev->dev.parent = &pdev->dev; ddev->dev.bus = &dfl_bus_type; ddev->dev.release = release_dfl_dev; ddev->id = id; ret = dev_set_name(&ddev->dev, "dfl_dev.%d", id); if (ret) goto put_dev; ddev->type = fdata->type; ddev->feature_id = feature->id; ddev->revision = feature->revision; ddev->dfh_version = feature->dfh_version; ddev->cdev = fdata->dfl_cdev; if (feature->param_size) { ddev->params = kmemdup(feature->params, feature->param_size, GFP_KERNEL); if (!ddev->params) { ret = -ENOMEM; goto put_dev; } ddev->param_size = feature->param_size; } /* add mmio resource */ parent_res = &pdev->resource[feature->resource_index]; ddev->mmio_res.flags = IORESOURCE_MEM; ddev->mmio_res.start = parent_res->start; ddev->mmio_res.end = parent_res->end; ddev->mmio_res.name = dev_name(&ddev->dev); ret = insert_resource(parent_res, &ddev->mmio_res); if (ret) { dev_err(&pdev->dev, "%s failed to claim resource: %pR\n", dev_name(&ddev->dev), &ddev->mmio_res); goto put_dev; } /* then add irq resource */ if (feature->nr_irqs) { ddev->irqs = kzalloc_objs(*ddev->irqs, feature->nr_irqs); if (!ddev->irqs) { ret = -ENOMEM; goto put_dev; } for (i = 0; i < feature->nr_irqs; i++) ddev->irqs[i] = feature->irq_ctx[i].irq; ddev->num_irqs = feature->nr_irqs; } ret = device_add(&ddev->dev); if (ret) goto put_dev; dev_dbg(&pdev->dev, "add dfl_dev: %s\n", dev_name(&ddev->dev)); return ddev; put_dev: /* calls release_dfl_dev() which does the clean up */ put_device(&ddev->dev); return ERR_PTR(ret);} static void dfl_devs_remove(struct dfl_feature_dev_data *fdata){ struct dfl_feature *feature; dfl_fpga_dev_for_each_feature(fdata, feature) { if (feature->ddev) { device_unregister(&feature->ddev->dev); feature->ddev = NULL; } }} static int dfl_devs_add(struct dfl_feature_dev_data *fdata){ struct dfl_feature *feature; struct dfl_device *ddev; int ret; dfl_fpga_dev_for_each_feature(fdata, feature) { if (feature->ioaddr) continue; if (feature->ddev) { ret = -EEXIST; goto err; } ddev = dfl_dev_add(fdata, feature); if (IS_ERR(ddev)) { ret = PTR_ERR(ddev); goto err; } feature->ddev = ddev; } return 0; err: dfl_devs_remove(fdata); return ret;} int __dfl_driver_register(struct dfl_driver *dfl_drv, struct module *owner){ if (!dfl_drv || !dfl_drv->probe || !dfl_drv->id_table) return -EINVAL; dfl_drv->drv.owner = owner; dfl_drv->drv.bus = &dfl_bus_type; return driver_register(&dfl_drv->drv);}EXPORT_SYMBOL(__dfl_driver_register); void dfl_driver_unregister(struct dfl_driver *dfl_drv){ driver_unregister(&dfl_drv->drv);}EXPORT_SYMBOL(dfl_driver_unregister); #define is_header_feature(feature) ((feature)->id == FEATURE_ID_FIU_HEADER) /** * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device * @pdev: feature device. */void dfl_fpga_dev_feature_uinit(struct platform_device *pdev){ struct dfl_feature_dev_data *fdata = to_dfl_feature_dev_data(&pdev->dev); struct dfl_feature *feature; dfl_devs_remove(fdata); dfl_fpga_dev_for_each_feature(fdata, feature) { if (feature->ops) {Lines 1–500 of 2027. Load more
// SPDX-License-Identifier: GPL-2.0 /* * Driver for FPGA Device Feature List (DFL) Support * * Copyright (C) 2017-2018 Intel Corporation, Inc. * * Authors: * Kang Luwei <luwei.kang@intel.com> * Zhang Yi <yi.z.zhang@intel.com> * Wu Hao <hao.wu@intel.com> * Xiao Guangrong <guangrong.xiao@linux.intel.com> */ #include <linux/dfl.h> #include <linux/fpga-dfl.h> #include <linux/module.h> #include <linux/overflow.h> #include <linux/uaccess.h> #include "dfl.h" static DEFINE_MUTEX(dfl_id_mutex); /* * when adding a new feature dev support in DFL framework, it's required to * add a new item in enum dfl_id_type and provide related information in below * dfl_devs table which is indexed by dfl_id_type, e.g. name string used for * platform device creation (define name strings in dfl.h, as they could be * reused by platform device drivers). * * if the new feature dev needs chardev support, then it's required to add * a new item in dfl_chardevs table and configure dfl_devs[i].devt_type as * index to dfl_chardevs table. If no chardev support just set devt_type * as one invalid index (DFL_FPGA_DEVT_MAX). */ enum dfl_fpga_devt_type { DFL_FPGA_DEVT_FME, DFL_FPGA_DEVT_PORT, DFL_FPGA_DEVT_MAX, }; static struct lock_class_key dfl_pdata_keys[DFL_ID_MAX]; static const char *dfl_pdata_key_strings[DFL_ID_MAX] = { "dfl-fme-pdata", "dfl-port-pdata", }; /** * struct dfl_dev_info - dfl feature device information. * @name: name string of the feature platform device. * @dfh_id: id value in Device Feature Header (DFH) register by DFL spec. * @id: idr id of the feature dev. * @devt_type: index to dfl_chrdevs[]. */ struct dfl_dev_info { const char *name; u16 dfh_id; struct idr id; enum dfl_fpga_devt_type devt_type; }; /* it is indexed by dfl_id_type */ static struct dfl_dev_info dfl_devs[] = { {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME, .devt_type = DFL_FPGA_DEVT_FME}, {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT, .devt_type = DFL_FPGA_DEVT_PORT}, }; /** * struct dfl_chardev_info - chardev information of dfl feature device * @name: nmae string of the char device. * @devt: devt of the char device. */ struct dfl_chardev_info { const char *name; dev_t devt; }; /* indexed by enum dfl_fpga_devt_type */ static struct dfl_chardev_info dfl_chrdevs[] = { {.name = DFL_FPGA_FEATURE_DEV_FME}, {.name = DFL_FPGA_FEATURE_DEV_PORT}, }; static void dfl_ids_init(void) { int i; for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) idr_init(&dfl_devs[i].id); } static void dfl_ids_destroy(void) { int i; for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) idr_destroy(&dfl_devs[i].id); } static int dfl_id_alloc(enum dfl_id_type type, struct device *dev) { int id; WARN_ON(type >= DFL_ID_MAX); mutex_lock(&dfl_id_mutex); id = idr_alloc(&dfl_devs[type].id, dev, 0, 0, GFP_KERNEL); mutex_unlock(&dfl_id_mutex); return id; } static void dfl_id_free(enum dfl_id_type type, int id) { WARN_ON(type >= DFL_ID_MAX); mutex_lock(&dfl_id_mutex); idr_remove(&dfl_devs[type].id, id); mutex_unlock(&dfl_id_mutex); } static enum dfl_id_type dfh_id_to_type(u16 id) { int i; for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) if (dfl_devs[i].dfh_id == id) return i; return DFL_ID_MAX; } /* * introduce a global port_ops list, it allows port drivers to register ops * in such list, then other feature devices (e.g. FME), could use the port * functions even related port platform device is hidden. Below is one example, * in virtualization case of PCIe-based FPGA DFL device, when SRIOV is * enabled, port (and it's AFU) is turned into VF and port platform device * is hidden from system but it's still required to access port to finish FPGA * reconfiguration function in FME. */ static DEFINE_MUTEX(dfl_port_ops_mutex); static LIST_HEAD(dfl_port_ops_list); /** * dfl_fpga_port_ops_get - get matched port ops from the global list * @fdata: feature dev data to match with associated port ops. * Return: matched port ops on success, NULL otherwise. * * Please note that must dfl_fpga_port_ops_put after use the port_ops. */ struct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct dfl_feature_dev_data *fdata) { struct dfl_fpga_port_ops *ops = NULL; mutex_lock(&dfl_port_ops_mutex); if (list_empty(&dfl_port_ops_list)) goto done; list_for_each_entry(ops, &dfl_port_ops_list, node) { /* match port_ops using the name of platform device */ if (!strcmp(fdata->pdev_name, ops->name)) { if (!try_module_get(ops->owner)) ops = NULL; goto done; } } ops = NULL; done: mutex_unlock(&dfl_port_ops_mutex); return ops; } EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_get); /** * dfl_fpga_port_ops_put - put port ops * @ops: port ops. */ void dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops) { if (ops && ops->owner) module_put(ops->owner); } EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_put); /** * dfl_fpga_port_ops_add - add port_ops to global list * @ops: port ops to add. */ void dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops) { mutex_lock(&dfl_port_ops_mutex); list_add_tail(&ops->node, &dfl_port_ops_list); mutex_unlock(&dfl_port_ops_mutex); } EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_add); /** * dfl_fpga_port_ops_del - remove port_ops from global list * @ops: port ops to del. */ void dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops) { mutex_lock(&dfl_port_ops_mutex); list_del(&ops->node); mutex_unlock(&dfl_port_ops_mutex); } EXPORT_SYMBOL_GPL(dfl_fpga_port_ops_del); /** * dfl_fpga_check_port_id - check the port id * @fdata: port feature dev data. * @pport_id: port id to compare. * * Return: 1 if port device matches with given port id, otherwise 0. */ int dfl_fpga_check_port_id(struct dfl_feature_dev_data *fdata, void *pport_id) { struct dfl_fpga_port_ops *port_ops; if (fdata->id != FEATURE_DEV_ID_UNUSED) return fdata->id == *(int *)pport_id; port_ops = dfl_fpga_port_ops_get(fdata); if (!port_ops || !port_ops->get_id) return 0; fdata->id = port_ops->get_id(fdata); dfl_fpga_port_ops_put(port_ops); return fdata->id == *(int *)pport_id; } EXPORT_SYMBOL_GPL(dfl_fpga_check_port_id); static DEFINE_IDA(dfl_device_ida); static const struct dfl_device_id * dfl_match_one_device(const struct dfl_device_id *id, struct dfl_device *ddev) { if (id->type == ddev->type && id->feature_id == ddev->feature_id) return id; return NULL; } static int dfl_bus_match(struct device *dev, const struct device_driver *drv) { struct dfl_device *ddev = to_dfl_dev(dev); const struct dfl_driver *ddrv = to_dfl_drv(drv); const struct dfl_device_id *id_entry; id_entry = ddrv->id_table; if (id_entry) { while (id_entry->feature_id) { if (dfl_match_one_device(id_entry, ddev)) { ddev->id_entry = id_entry; return 1; } id_entry++; } } return 0; } static int dfl_bus_probe(struct device *dev) { struct dfl_driver *ddrv = to_dfl_drv(dev->driver); struct dfl_device *ddev = to_dfl_dev(dev); return ddrv->probe(ddev); } static void dfl_bus_remove(struct device *dev) { struct dfl_driver *ddrv = to_dfl_drv(dev->driver); struct dfl_device *ddev = to_dfl_dev(dev); if (ddrv->remove) ddrv->remove(ddev); } static int dfl_bus_uevent(const struct device *dev, struct kobj_uevent_env *env) { const struct dfl_device *ddev = to_dfl_dev(dev); return add_uevent_var(env, "MODALIAS=dfl:t%04Xf%04X", ddev->type, ddev->feature_id); } static ssize_t type_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dfl_device *ddev = to_dfl_dev(dev); return sprintf(buf, "0x%x\n", ddev->type); } static DEVICE_ATTR_RO(type); static ssize_t feature_id_show(struct device *dev, struct device_attribute *attr, char *buf) { struct dfl_device *ddev = to_dfl_dev(dev); return sprintf(buf, "0x%x\n", ddev->feature_id); } static DEVICE_ATTR_RO(feature_id); static struct attribute *dfl_dev_attrs[] = { &dev_attr_type.attr, &dev_attr_feature_id.attr, NULL, }; ATTRIBUTE_GROUPS(dfl_dev); static const struct bus_type dfl_bus_type = { .name = "dfl", .match = dfl_bus_match, .probe = dfl_bus_probe, .remove = dfl_bus_remove, .uevent = dfl_bus_uevent, .dev_groups = dfl_dev_groups, }; static void release_dfl_dev(struct device *dev) { struct dfl_device *ddev = to_dfl_dev(dev); if (ddev->mmio_res.parent) release_resource(&ddev->mmio_res); kfree(ddev->params); ida_free(&dfl_device_ida, ddev->id); kfree(ddev->irqs); kfree(ddev); } static struct dfl_device * dfl_dev_add(struct dfl_feature_dev_data *fdata, struct dfl_feature *feature) { struct platform_device *pdev = fdata->dev; struct resource *parent_res; struct dfl_device *ddev; int id, i, ret; ddev = kzalloc_obj(*ddev); if (!ddev) return ERR_PTR(-ENOMEM); id = ida_alloc(&dfl_device_ida, GFP_KERNEL); if (id < 0) { dev_err(&pdev->dev, "unable to get id\n"); kfree(ddev); return ERR_PTR(id); } /* freeing resources by put_device() after device_initialize() */ device_initialize(&ddev->dev); ddev->dev.parent = &pdev->dev; ddev->dev.bus = &dfl_bus_type; ddev->dev.release = release_dfl_dev; ddev->id = id; ret = dev_set_name(&ddev->dev, "dfl_dev.%d", id); if (ret) goto put_dev; ddev->type = fdata->type; ddev->feature_id = feature->id; ddev->revision = feature->revision; ddev->dfh_version = feature->dfh_version; ddev->cdev = fdata->dfl_cdev; if (feature->param_size) { ddev->params = kmemdup(feature->params, feature->param_size, GFP_KERNEL); if (!ddev->params) { ret = -ENOMEM; goto put_dev; } ddev->param_size = feature->param_size; } /* add mmio resource */ parent_res = &pdev->resource[feature->resource_index]; ddev->mmio_res.flags = IORESOURCE_MEM; ddev->mmio_res.start = parent_res->start; ddev->mmio_res.end = parent_res->end; ddev->mmio_res.name = dev_name(&ddev->dev); ret = insert_resource(parent_res, &ddev->mmio_res); if (ret) { dev_err(&pdev->dev, "%s failed to claim resource: %pR\n", dev_name(&ddev->dev), &ddev->mmio_res); goto put_dev; } /* then add irq resource */ if (feature->nr_irqs) { ddev->irqs = kzalloc_objs(*ddev->irqs, feature->nr_irqs); if (!ddev->irqs) { ret = -ENOMEM; goto put_dev; } for (i = 0; i < feature->nr_irqs; i++) ddev->irqs[i] = feature->irq_ctx[i].irq; ddev->num_irqs = feature->nr_irqs; } ret = device_add(&ddev->dev); if (ret) goto put_dev; dev_dbg(&pdev->dev, "add dfl_dev: %s\n", dev_name(&ddev->dev)); return ddev; put_dev: /* calls release_dfl_dev() which does the clean up */ put_device(&ddev->dev); return ERR_PTR(ret); } static void dfl_devs_remove(struct dfl_feature_dev_data *fdata) { struct dfl_feature *feature; dfl_fpga_dev_for_each_feature(fdata, feature) { if (feature->ddev) { device_unregister(&feature->ddev->dev); feature->ddev = NULL; } } } static int dfl_devs_add(struct dfl_feature_dev_data *fdata) { struct dfl_feature *feature; struct dfl_device *ddev; int ret; dfl_fpga_dev_for_each_feature(fdata, feature) { if (feature->ioaddr) continue; if (feature->ddev) { ret = -EEXIST; goto err; } ddev = dfl_dev_add(fdata, feature); if (IS_ERR(ddev)) { ret = PTR_ERR(ddev); goto err; } feature->ddev = ddev; } return 0; err: dfl_devs_remove(fdata); return ret; } int __dfl_driver_register(struct dfl_driver *dfl_drv, struct module *owner) { if (!dfl_drv || !dfl_drv->probe || !dfl_drv->id_table) return -EINVAL; dfl_drv->drv.owner = owner; dfl_drv->drv.bus = &dfl_bus_type; return driver_register(&dfl_drv->drv); } EXPORT_SYMBOL(__dfl_driver_register); void dfl_driver_unregister(struct dfl_driver *dfl_drv) { driver_unregister(&dfl_drv->drv); } EXPORT_SYMBOL(dfl_driver_unregister); #define is_header_feature(feature) ((feature)->id == FEATURE_ID_FIU_HEADER) /** * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device * @pdev: feature device. */ void dfl_fpga_dev_feature_uinit(struct platform_device *pdev) { struct dfl_feature_dev_data *fdata = to_dfl_feature_dev_data(&pdev->dev); struct dfl_feature *feature; dfl_devs_remove(fdata); dfl_fpga_dev_for_each_feature(fdata, feature) { if (feature->ops) {