2027 lines · 48.7 KB · C
RawDownload
// 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.