From 102a0743326a03cd1a1202ceda21e175b7d3575c Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Tue, 20 Feb 2024 01:20:52 +0000 Subject: [PATCH] add new system file --- kernel/drivers/fpga/dfl.c | 1029 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 928 insertions(+), 101 deletions(-) diff --git a/kernel/drivers/fpga/dfl.c b/kernel/drivers/fpga/dfl.c index ab361ec..eb8a6e3 100644 --- a/kernel/drivers/fpga/dfl.c +++ b/kernel/drivers/fpga/dfl.c @@ -10,7 +10,9 @@ * Wu Hao <hao.wu@intel.com> * Xiao Guangrong <guangrong.xiao@linux.intel.com> */ +#include <linux/fpga-dfl.h> #include <linux/module.h> +#include <linux/uaccess.h> #include "dfl.h" @@ -28,12 +30,6 @@ * index to dfl_chardevs table. If no chardev support just set devt_type * as one invalid index (DFL_FPGA_DEVT_MAX). */ -enum dfl_id_type { - FME_ID, /* fme id allocation and mapping */ - PORT_ID, /* port id allocation and mapping */ - DFL_ID_MAX, -}; - enum dfl_fpga_devt_type { DFL_FPGA_DEVT_FME, DFL_FPGA_DEVT_PORT, @@ -56,7 +52,7 @@ */ struct dfl_dev_info { const char *name; - u32 dfh_id; + u16 dfh_id; struct idr id; enum dfl_fpga_devt_type devt_type; }; @@ -132,7 +128,7 @@ return DFL_ID_MAX; } -static enum dfl_id_type dfh_id_to_type(u32 id) +static enum dfl_id_type dfh_id_to_type(u16 id) { int i; @@ -231,18 +227,265 @@ */ int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id) { - struct dfl_fpga_port_ops *port_ops = dfl_fpga_port_ops_get(pdev); - int port_id; + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fpga_port_ops *port_ops; + if (pdata->id != FEATURE_DEV_ID_UNUSED) + return pdata->id == *(int *)pport_id; + + port_ops = dfl_fpga_port_ops_get(pdev); if (!port_ops || !port_ops->get_id) return 0; - port_id = port_ops->get_id(pdev); + pdata->id = port_ops->get_id(pdev); dfl_fpga_port_ops_put(port_ops); - return port_id == *(int *)pport_id; + return pdata->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, struct device_driver *drv) +{ + struct dfl_device *ddev = to_dfl_dev(dev); + 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 int 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); + + return 0; +} + +static int dfl_bus_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct dfl_device *ddev = to_dfl_dev(dev); + + /* The type has 4 valid bits and feature_id has 12 valid bits */ + return add_uevent_var(env, "MODALIAS=dfl:t%01Xf%03X", + 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 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); + + ida_simple_remove(&dfl_device_ida, ddev->id); + kfree(ddev->irqs); + kfree(ddev); +} + +static struct dfl_device * +dfl_dev_add(struct dfl_feature_platform_data *pdata, + struct dfl_feature *feature) +{ + struct platform_device *pdev = pdata->dev; + struct resource *parent_res; + struct dfl_device *ddev; + int id, i, ret; + + ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); + if (!ddev) + return ERR_PTR(-ENOMEM); + + id = ida_simple_get(&dfl_device_ida, 0, 0, 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 = feature_dev_id_type(pdev); + ddev->feature_id = feature->id; + ddev->cdev = pdata->dfl_cdev; + + /* 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 = kcalloc(feature->nr_irqs, + sizeof(*ddev->irqs), GFP_KERNEL); + 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_platform_data *pdata) +{ + struct dfl_feature *feature; + + dfl_fpga_dev_for_each_feature(pdata, feature) { + if (feature->ddev) { + device_unregister(&feature->ddev->dev); + feature->ddev = NULL; + } + } +} + +static int dfl_devs_add(struct dfl_feature_platform_data *pdata) +{ + struct dfl_feature *feature; + struct dfl_device *ddev; + int ret; + + dfl_fpga_dev_for_each_feature(pdata, feature) { + if (feature->ioaddr) + continue; + + if (feature->ddev) { + ret = -EEXIST; + goto err; + } + + ddev = dfl_dev_add(pdata, feature); + if (IS_ERR(ddev)) { + ret = PTR_ERR(ddev); + goto err; + } + + feature->ddev = ddev; + } + + return 0; + +err: + dfl_devs_remove(pdata); + 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 @@ -253,11 +496,15 @@ struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); struct dfl_feature *feature; - dfl_fpga_dev_for_each_feature(pdata, feature) + dfl_devs_remove(pdata); + + dfl_fpga_dev_for_each_feature(pdata, feature) { if (feature->ops) { - feature->ops->uinit(pdev, feature); + if (feature->ops->uinit) + feature->ops->uinit(pdev, feature); feature->ops = NULL; } + } } EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_uinit); @@ -266,15 +513,46 @@ struct dfl_feature *feature, struct dfl_feature_driver *drv) { - int ret; + void __iomem *base; + int ret = 0; - ret = drv->ops->init(pdev, feature); - if (ret) - return ret; + if (!is_header_feature(feature)) { + base = devm_platform_ioremap_resource(pdev, + feature->resource_index); + if (IS_ERR(base)) { + dev_err(&pdev->dev, + "ioremap failed for feature 0x%x!\n", + feature->id); + return PTR_ERR(base); + } + + feature->ioaddr = base; + } + + if (drv->ops->init) { + ret = drv->ops->init(pdev, feature); + if (ret) + return ret; + } feature->ops = drv->ops; return ret; +} + +static bool dfl_feature_drv_match(struct dfl_feature *feature, + struct dfl_feature_driver *driver) +{ + const struct dfl_feature_id *ids = driver->id_table; + + if (ids) { + while (ids->id) { + if (ids->id == feature->id) + return true; + ids++; + } + } + return false; } /** @@ -297,8 +575,7 @@ while (drv->ops) { dfl_fpga_dev_for_each_feature(pdata, feature) { - /* match feature and drv using id */ - if (feature->id == drv->id) { + if (dfl_feature_drv_match(feature, drv)) { ret = dfl_feature_instance_init(pdev, pdata, feature, drv); if (ret) @@ -307,6 +584,10 @@ } drv++; } + + ret = dfl_devs_add(pdata); + if (ret) + goto exit; return 0; exit: @@ -322,7 +603,7 @@ for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) if (MAJOR(dfl_chrdevs[i].devt)) { unregister_chrdev_region(dfl_chrdevs[i].devt, - MINORMASK); + MINORMASK + 1); dfl_chrdevs[i].devt = MKDEV(0, 0); } } @@ -332,8 +613,8 @@ int i, ret; for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) { - ret = alloc_chrdev_region(&dfl_chrdevs[i].devt, 0, MINORMASK, - dfl_chrdevs[i].name); + ret = alloc_chrdev_region(&dfl_chrdevs[i].devt, 0, + MINORMASK + 1, dfl_chrdevs[i].name); if (ret) goto exit; } @@ -400,16 +681,26 @@ * * @dev: device to enumerate. * @cdev: the container device for all feature devices. + * @nr_irqs: number of irqs for all feature devices. + * @irq_table: Linux IRQ numbers for all irqs, indexed by local irq index of + * this device. * @feature_dev: current feature device. - * @ioaddr: header register region address of feature device in enumeration. + * @ioaddr: header register region address of current FIU in enumeration. + * @start: register resource start of current FIU. + * @len: max register resource length of current FIU. * @sub_features: a sub features linked list for feature device in enumeration. * @feature_num: number of sub features for feature device in enumeration. */ struct build_feature_devs_info { struct device *dev; struct dfl_fpga_cdev *cdev; + unsigned int nr_irqs; + int *irq_table; + struct platform_device *feature_dev; void __iomem *ioaddr; + resource_size_t start; + resource_size_t len; struct list_head sub_features; int feature_num; }; @@ -421,12 +712,16 @@ * @mmio_res: mmio resource of this sub feature. * @ioaddr: mapped base address of mmio resource. * @node: node in sub_features linked list. + * @irq_base: start of irq index in this sub feature. + * @nr_irqs: number of irqs of this sub feature. */ struct dfl_feature_info { - u64 fid; + u16 fid; struct resource mmio_res; void __iomem *ioaddr; struct list_head node; + unsigned int irq_base; + unsigned int nr_irqs; }; static void dfl_fpga_cdev_add_port_dev(struct dfl_fpga_cdev *cdev, @@ -451,10 +746,7 @@ struct dfl_feature_platform_data *pdata; struct dfl_feature_info *finfo, *p; enum dfl_id_type type; - int ret, index = 0; - - if (!fdev) - return 0; + int ret, index = 0, res_idx = 0; type = feature_dev_id_type(fdev); if (WARN_ON_ONCE(type >= DFL_ID_MAX)) @@ -466,14 +758,14 @@ * it will be automatically freed by device's release() callback, * platform_device_release(). */ - pdata = kzalloc(dfl_feature_platform_data_size(binfo->feature_num), - GFP_KERNEL); + pdata = kzalloc(struct_size(pdata, features, binfo->feature_num), GFP_KERNEL); if (!pdata) return -ENOMEM; pdata->dev = fdev; pdata->num = binfo->feature_num; pdata->dfl_cdev = binfo->cdev; + pdata->id = FEATURE_DEV_ID_UNUSED; mutex_init(&pdata->lock); lockdep_set_class_and_name(&pdata->lock, &dfl_pdata_keys[type], dfl_pdata_key_strings[type]); @@ -497,13 +789,46 @@ /* fill features and resource information for feature dev */ list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { - struct dfl_feature *feature = &pdata->features[index]; + struct dfl_feature *feature = &pdata->features[index++]; + struct dfl_feature_irq_ctx *ctx; + unsigned int i; /* save resource information for each feature */ + feature->dev = fdev; feature->id = finfo->fid; - feature->resource_index = index; - feature->ioaddr = finfo->ioaddr; - fdev->resource[index++] = finfo->mmio_res; + + /* + * the FIU header feature has some fundamental functions (sriov + * set, port enable/disable) needed for the dfl bus device and + * other sub features. So its mmio resource should be mapped by + * DFL bus device. And we should not assign it to feature + * devices (dfl-fme/afu) again. + */ + if (is_header_feature(feature)) { + feature->resource_index = -1; + feature->ioaddr = + devm_ioremap_resource(binfo->dev, + &finfo->mmio_res); + if (IS_ERR(feature->ioaddr)) + return PTR_ERR(feature->ioaddr); + } else { + feature->resource_index = res_idx; + fdev->resource[res_idx++] = finfo->mmio_res; + } + + if (finfo->nr_irqs) { + ctx = devm_kcalloc(binfo->dev, finfo->nr_irqs, + sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + for (i = 0; i < finfo->nr_irqs; i++) + ctx[i].irq = + binfo->irq_table[finfo->irq_base + i]; + + feature->irq_ctx = ctx; + feature->nr_irqs = finfo->nr_irqs; + } list_del(&finfo->node); kfree(finfo); @@ -532,18 +857,12 @@ static int build_info_create_dev(struct build_feature_devs_info *binfo, - enum dfl_id_type type, void __iomem *ioaddr) + enum dfl_id_type type) { struct platform_device *fdev; - int ret; if (type >= DFL_ID_MAX) return -EINVAL; - - /* we will create a new device, commit current device first */ - ret = build_info_commit_dev(binfo); - if (ret) - return ret; /* * we use -ENODEV as the initialization indicator which indicates @@ -555,7 +874,7 @@ binfo->feature_dev = fdev; binfo->feature_num = 0; - binfo->ioaddr = ioaddr; + INIT_LIST_HEAD(&binfo->sub_features); fdev->id = dfl_id_alloc(type, &fdev->dev); @@ -599,7 +918,7 @@ return ofst ? ofst : 4096; } -static u64 feature_id(void __iomem *start) +static u16 feature_id(void __iomem *start) { u64 v = readq(start + DFH); u16 id = FIELD_GET(DFH_ID, v); @@ -616,6 +935,78 @@ return 0; } +static int parse_feature_irqs(struct build_feature_devs_info *binfo, + resource_size_t ofst, u16 fid, + unsigned int *irq_base, unsigned int *nr_irqs) +{ + void __iomem *base = binfo->ioaddr + ofst; + unsigned int i, ibase, inr = 0; + int virq; + u64 v; + + /* + * Ideally DFL framework should only read info from DFL header, but + * current version DFL only provides mmio resources information for + * each feature in DFL Header, no field for interrupt resources. + * Interrupt resource information is provided by specific mmio + * registers of each private feature which supports interrupt. So in + * order to parse and assign irq resources, DFL framework has to look + * into specific capability registers of these private features. + * + * Once future DFL version supports generic interrupt resource + * information in common DFL headers, the generic interrupt parsing + * code will be added. But in order to be compatible to old version + * DFL, the driver may still fall back to these quirks. + */ + switch (fid) { + case PORT_FEATURE_ID_UINT: + v = readq(base + PORT_UINT_CAP); + ibase = FIELD_GET(PORT_UINT_CAP_FST_VECT, v); + inr = FIELD_GET(PORT_UINT_CAP_INT_NUM, v); + break; + case PORT_FEATURE_ID_ERROR: + v = readq(base + PORT_ERROR_CAP); + ibase = FIELD_GET(PORT_ERROR_CAP_INT_VECT, v); + inr = FIELD_GET(PORT_ERROR_CAP_SUPP_INT, v); + break; + case FME_FEATURE_ID_GLOBAL_ERR: + v = readq(base + FME_ERROR_CAP); + ibase = FIELD_GET(FME_ERROR_CAP_INT_VECT, v); + inr = FIELD_GET(FME_ERROR_CAP_SUPP_INT, v); + break; + } + + if (!inr) { + *irq_base = 0; + *nr_irqs = 0; + return 0; + } + + dev_dbg(binfo->dev, "feature: 0x%x, irq_base: %u, nr_irqs: %u\n", + fid, ibase, inr); + + if (ibase + inr > binfo->nr_irqs) { + dev_err(binfo->dev, + "Invalid interrupt number in feature 0x%x\n", fid); + return -EINVAL; + } + + for (i = 0; i < inr; i++) { + virq = binfo->irq_table[ibase + i]; + if (virq < 0 || virq > NR_IRQS) { + dev_err(binfo->dev, + "Invalid irq table entry for feature 0x%x\n", + fid); + return -EINVAL; + } + } + + *irq_base = ibase; + *nr_irqs = inr; + + return 0; +} + /* * when create sub feature instances, for private features, it doesn't need * to provide resource size and feature id as they could be read from DFH @@ -625,27 +1016,33 @@ */ static int create_feature_instance(struct build_feature_devs_info *binfo, - struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst, - resource_size_t size, u64 fid) + resource_size_t ofst, resource_size_t size, u16 fid) { + unsigned int irq_base, nr_irqs; struct dfl_feature_info *finfo; + int ret; /* read feature size and id if inputs are invalid */ - size = size ? size : feature_size(dfl->ioaddr + ofst); - fid = fid ? fid : feature_id(dfl->ioaddr + ofst); + size = size ? size : feature_size(binfo->ioaddr + ofst); + fid = fid ? fid : feature_id(binfo->ioaddr + ofst); - if (dfl->len - ofst < size) + if (binfo->len - ofst < size) return -EINVAL; + + ret = parse_feature_irqs(binfo, ofst, fid, &irq_base, &nr_irqs); + if (ret) + return ret; finfo = kzalloc(sizeof(*finfo), GFP_KERNEL); if (!finfo) return -ENOMEM; finfo->fid = fid; - finfo->mmio_res.start = dfl->start + ofst; + finfo->mmio_res.start = binfo->start + ofst; finfo->mmio_res.end = finfo->mmio_res.start + size - 1; finfo->mmio_res.flags = IORESOURCE_MEM; - finfo->ioaddr = dfl->ioaddr + ofst; + finfo->irq_base = irq_base; + finfo->nr_irqs = nr_irqs; list_add_tail(&finfo->node, &binfo->sub_features); binfo->feature_num++; @@ -654,7 +1051,6 @@ } static int parse_feature_port_afu(struct build_feature_devs_info *binfo, - struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) { u64 v = readq(binfo->ioaddr + PORT_HDR_CAP); @@ -662,21 +1058,22 @@ WARN_ON(!size); - return create_feature_instance(binfo, dfl, ofst, size, FEATURE_ID_AFU); + return create_feature_instance(binfo, ofst, size, FEATURE_ID_AFU); } +#define is_feature_dev_detected(binfo) (!!(binfo)->feature_dev) + static int parse_feature_afu(struct build_feature_devs_info *binfo, - struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) { - if (!binfo->feature_dev) { + if (!is_feature_dev_detected(binfo)) { dev_err(binfo->dev, "this AFU does not belong to any FIU.\n"); return -EINVAL; } switch (feature_dev_id_type(binfo->feature_dev)) { case PORT_ID: - return parse_feature_port_afu(binfo, dfl, ofst); + return parse_feature_port_afu(binfo, ofst); default: dev_info(binfo->dev, "AFU belonging to FIU %s is not supported yet.\n", binfo->feature_dev->name); @@ -685,35 +1082,79 @@ return 0; } +static int build_info_prepare(struct build_feature_devs_info *binfo, + resource_size_t start, resource_size_t len) +{ + struct device *dev = binfo->dev; + void __iomem *ioaddr; + + if (!devm_request_mem_region(dev, start, len, dev_name(dev))) { + dev_err(dev, "request region fail, start:%pa, len:%pa\n", + &start, &len); + return -EBUSY; + } + + ioaddr = devm_ioremap(dev, start, len); + if (!ioaddr) { + dev_err(dev, "ioremap region fail, start:%pa, len:%pa\n", + &start, &len); + return -ENOMEM; + } + + binfo->start = start; + binfo->len = len; + binfo->ioaddr = ioaddr; + + return 0; +} + +static void build_info_complete(struct build_feature_devs_info *binfo) +{ + devm_iounmap(binfo->dev, binfo->ioaddr); + devm_release_mem_region(binfo->dev, binfo->start, binfo->len); +} + static int parse_feature_fiu(struct build_feature_devs_info *binfo, - struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) { - u32 id, offset; - u64 v; int ret = 0; + u32 offset; + u16 id; + u64 v; - v = readq(dfl->ioaddr + ofst + DFH); + if (is_feature_dev_detected(binfo)) { + build_info_complete(binfo); + + ret = build_info_commit_dev(binfo); + if (ret) + return ret; + + ret = build_info_prepare(binfo, binfo->start + ofst, + binfo->len - ofst); + if (ret) + return ret; + } + + v = readq(binfo->ioaddr + DFH); id = FIELD_GET(DFH_ID, v); /* create platform device for dfl feature dev */ - ret = build_info_create_dev(binfo, dfh_id_to_type(id), - dfl->ioaddr + ofst); + ret = build_info_create_dev(binfo, dfh_id_to_type(id)); if (ret) return ret; - ret = create_feature_instance(binfo, dfl, ofst, 0, 0); + ret = create_feature_instance(binfo, 0, 0, 0); if (ret) return ret; /* * find and parse FIU's child AFU via its NEXT_AFU register. * please note that only Port has valid NEXT_AFU pointer per spec. */ - v = readq(dfl->ioaddr + ofst + NEXT_AFU); + v = readq(binfo->ioaddr + NEXT_AFU); offset = FIELD_GET(NEXT_AFU_NEXT_DFH_OFST, v); if (offset) - return parse_feature_afu(binfo, dfl, ofst + offset); + return parse_feature_afu(binfo, offset); dev_dbg(binfo->dev, "No AFUs detected on FIU %d\n", id); @@ -721,41 +1162,39 @@ } static int parse_feature_private(struct build_feature_devs_info *binfo, - struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) { - if (!binfo->feature_dev) { - dev_err(binfo->dev, "the private feature %llx does not belong to any AFU.\n", - (unsigned long long)feature_id(dfl->ioaddr + ofst)); + if (!is_feature_dev_detected(binfo)) { + dev_err(binfo->dev, "the private feature 0x%x does not belong to any AFU.\n", + feature_id(binfo->ioaddr + ofst)); return -EINVAL; } - return create_feature_instance(binfo, dfl, ofst, 0, 0); + return create_feature_instance(binfo, ofst, 0, 0); } /** * parse_feature - parse a feature on given device feature list * * @binfo: build feature devices information. - * @dfl: device feature list to parse - * @ofst: offset to feature header on this device feature list + * @ofst: offset to current FIU header */ static int parse_feature(struct build_feature_devs_info *binfo, - struct dfl_fpga_enum_dfl *dfl, resource_size_t ofst) + resource_size_t ofst) { u64 v; u32 type; - v = readq(dfl->ioaddr + ofst + DFH); + v = readq(binfo->ioaddr + ofst + DFH); type = FIELD_GET(DFH_TYPE, v); switch (type) { case DFH_TYPE_AFU: - return parse_feature_afu(binfo, dfl, ofst); + return parse_feature_afu(binfo, ofst); case DFH_TYPE_PRIVATE: - return parse_feature_private(binfo, dfl, ofst); + return parse_feature_private(binfo, ofst); case DFH_TYPE_FIU: - return parse_feature_fiu(binfo, dfl, ofst); + return parse_feature_fiu(binfo, ofst); default: dev_info(binfo->dev, "Feature Type %x is not supported.\n", type); @@ -765,13 +1204,16 @@ } static int parse_feature_list(struct build_feature_devs_info *binfo, - struct dfl_fpga_enum_dfl *dfl) + resource_size_t start, resource_size_t len) { - void __iomem *start = dfl->ioaddr; - void __iomem *end = dfl->ioaddr + dfl->len; + resource_size_t end = start + len; int ret = 0; u32 ofst = 0; u64 v; + + ret = build_info_prepare(binfo, start, len); + if (ret) + return ret; /* walk through the device feature list via DFH's next DFH pointer. */ for (; start < end; start += ofst) { @@ -780,11 +1222,11 @@ return -EINVAL; } - ret = parse_feature(binfo, dfl, start - dfl->ioaddr); + ret = parse_feature(binfo, start - binfo->start); if (ret) return ret; - v = readq(start + DFH); + v = readq(binfo->ioaddr + start - binfo->start + DFH); ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); /* stop parsing if EOL(End of List) is set or offset is 0 */ @@ -793,7 +1235,12 @@ } /* commit current feature device when reach the end of list */ - return build_info_commit_dev(binfo); + build_info_complete(binfo); + + if (is_feature_dev_detected(binfo)) + ret = build_info_commit_dev(binfo); + + return ret; } struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev) @@ -831,6 +1278,10 @@ devm_kfree(dev, dfl); } + /* remove irq table */ + if (info->irq_table) + devm_kfree(dev, info->irq_table); + devm_kfree(dev, info); put_device(dev); } @@ -842,7 +1293,6 @@ * @info: ptr to dfl_fpga_enum_info * @start: mmio resource address of the device feature list. * @len: mmio resource length of the device feature list. - * @ioaddr: mapped mmio resource address of the device feature list. * * One FPGA device may have one or more Device Feature Lists (DFLs), use this * function to add information of each DFL to common data structure for next @@ -851,8 +1301,7 @@ * Return: 0 on success, negative error code otherwise. */ int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, - resource_size_t start, resource_size_t len, - void __iomem *ioaddr) + resource_size_t start, resource_size_t len) { struct dfl_fpga_enum_dfl *dfl; @@ -862,13 +1311,51 @@ dfl->start = start; dfl->len = len; - dfl->ioaddr = ioaddr; list_add_tail(&dfl->node, &info->dfls); return 0; } EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_add_dfl); + +/** + * dfl_fpga_enum_info_add_irq - add irq table to enum info + * + * @info: ptr to dfl_fpga_enum_info + * @nr_irqs: number of irqs of the DFL fpga device to be enumerated. + * @irq_table: Linux IRQ numbers for all irqs, indexed by local irq index of + * this device. + * + * One FPGA device may have several interrupts. This function adds irq + * information of the DFL fpga device to enum info for next step enumeration. + * This function should be called before dfl_fpga_feature_devs_enumerate(). + * As we only support one irq domain for all DFLs in the same enum info, adding + * irq table a second time for the same enum info will return error. + * + * If we need to enumerate DFLs which belong to different irq domains, we + * should fill more enum info and enumerate them one by one. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_enum_info_add_irq(struct dfl_fpga_enum_info *info, + unsigned int nr_irqs, int *irq_table) +{ + if (!nr_irqs || !irq_table) + return -EINVAL; + + if (info->irq_table) + return -EEXIST; + + info->irq_table = devm_kmemdup(info->dev, irq_table, + sizeof(int) * nr_irqs, GFP_KERNEL); + if (!info->irq_table) + return -ENOMEM; + + info->nr_irqs = nr_irqs; + + return 0; +} +EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_add_irq); static int remove_feature_dev(struct device *dev, void *data) { @@ -913,7 +1400,7 @@ if (!cdev) return ERR_PTR(-ENOMEM); - cdev->region = fpga_region_create(info->dev, NULL, NULL); + cdev->region = devm_fpga_region_create(info->dev, NULL, NULL); if (!cdev->region) { ret = -ENOMEM; goto free_cdev_exit; @@ -925,7 +1412,7 @@ ret = fpga_region_register(cdev->region); if (ret) - goto free_region_exit; + goto free_cdev_exit; /* create and init build info for enumeration */ binfo = devm_kzalloc(info->dev, sizeof(*binfo), GFP_KERNEL); @@ -937,12 +1424,16 @@ binfo->dev = info->dev; binfo->cdev = cdev; + binfo->nr_irqs = info->nr_irqs; + if (info->nr_irqs) + binfo->irq_table = info->irq_table; + /* * start enumeration for all feature devices based on Device Feature * Lists. */ list_for_each_entry(dfl, &info->dfls, node) { - ret = parse_feature_list(binfo, dfl); + ret = parse_feature_list(binfo, dfl->start, dfl->len); if (ret) { remove_feature_devs(cdev); build_info_free(binfo); @@ -956,8 +1447,6 @@ unregister_region_exit: fpga_region_unregister(cdev->region); -free_region_exit: - fpga_region_free(cdev->region); free_cdev_exit: devm_kfree(info->dev, cdev); return ERR_PTR(ret); @@ -975,24 +1464,26 @@ { struct dfl_feature_platform_data *pdata, *ptmp; - remove_feature_devs(cdev); - mutex_lock(&cdev->lock); - if (cdev->fme_dev) { - /* the fme should be unregistered. */ - WARN_ON(device_is_registered(cdev->fme_dev)); + if (cdev->fme_dev) put_device(cdev->fme_dev); - } list_for_each_entry_safe(pdata, ptmp, &cdev->port_dev_list, node) { struct platform_device *port_dev = pdata->dev; - /* the port should be unregistered. */ - WARN_ON(device_is_registered(&port_dev->dev)); + /* remove released ports */ + if (!device_is_registered(&port_dev->dev)) { + dfl_id_free(feature_dev_id_type(port_dev), + port_dev->id); + platform_device_put(port_dev); + } + list_del(&pdata->node); put_device(&port_dev->dev); } mutex_unlock(&cdev->lock); + + remove_feature_devs(cdev); fpga_region_unregister(cdev->region); devm_kfree(cdev->parent, cdev); @@ -1035,19 +1526,355 @@ { int ret; + ret = bus_register(&dfl_bus_type); + if (ret) + return ret; + dfl_ids_init(); ret = dfl_chardev_init(); - if (ret) + if (ret) { dfl_ids_destroy(); + bus_unregister(&dfl_bus_type); + } return ret; } + +/** + * dfl_fpga_cdev_release_port - release a port platform device + * + * @cdev: parent container device. + * @port_id: id of the port platform device. + * + * This function allows user to release a port platform device. This is a + * mandatory step before turn a port from PF into VF for SRIOV support. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_cdev_release_port(struct dfl_fpga_cdev *cdev, int port_id) +{ + struct dfl_feature_platform_data *pdata; + struct platform_device *port_pdev; + int ret = -ENODEV; + + mutex_lock(&cdev->lock); + port_pdev = __dfl_fpga_cdev_find_port(cdev, &port_id, + dfl_fpga_check_port_id); + if (!port_pdev) + goto unlock_exit; + + if (!device_is_registered(&port_pdev->dev)) { + ret = -EBUSY; + goto put_dev_exit; + } + + pdata = dev_get_platdata(&port_pdev->dev); + + mutex_lock(&pdata->lock); + ret = dfl_feature_dev_use_begin(pdata, true); + mutex_unlock(&pdata->lock); + if (ret) + goto put_dev_exit; + + platform_device_del(port_pdev); + cdev->released_port_num++; +put_dev_exit: + put_device(&port_pdev->dev); +unlock_exit: + mutex_unlock(&cdev->lock); + return ret; +} +EXPORT_SYMBOL_GPL(dfl_fpga_cdev_release_port); + +/** + * dfl_fpga_cdev_assign_port - assign a port platform device back + * + * @cdev: parent container device. + * @port_id: id of the port platform device. + * + * This function allows user to assign a port platform device back. This is + * a mandatory step after disable SRIOV support. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_cdev_assign_port(struct dfl_fpga_cdev *cdev, int port_id) +{ + struct dfl_feature_platform_data *pdata; + struct platform_device *port_pdev; + int ret = -ENODEV; + + mutex_lock(&cdev->lock); + port_pdev = __dfl_fpga_cdev_find_port(cdev, &port_id, + dfl_fpga_check_port_id); + if (!port_pdev) + goto unlock_exit; + + if (device_is_registered(&port_pdev->dev)) { + ret = -EBUSY; + goto put_dev_exit; + } + + ret = platform_device_add(port_pdev); + if (ret) + goto put_dev_exit; + + pdata = dev_get_platdata(&port_pdev->dev); + + mutex_lock(&pdata->lock); + dfl_feature_dev_use_end(pdata); + mutex_unlock(&pdata->lock); + + cdev->released_port_num--; +put_dev_exit: + put_device(&port_pdev->dev); +unlock_exit: + mutex_unlock(&cdev->lock); + return ret; +} +EXPORT_SYMBOL_GPL(dfl_fpga_cdev_assign_port); + +static void config_port_access_mode(struct device *fme_dev, int port_id, + bool is_vf) +{ + void __iomem *base; + u64 v; + + base = dfl_get_feature_ioaddr_by_id(fme_dev, FME_FEATURE_ID_HEADER); + + v = readq(base + FME_HDR_PORT_OFST(port_id)); + + v &= ~FME_PORT_OFST_ACC_CTRL; + v |= FIELD_PREP(FME_PORT_OFST_ACC_CTRL, + is_vf ? FME_PORT_OFST_ACC_VF : FME_PORT_OFST_ACC_PF); + + writeq(v, base + FME_HDR_PORT_OFST(port_id)); +} + +#define config_port_vf_mode(dev, id) config_port_access_mode(dev, id, true) +#define config_port_pf_mode(dev, id) config_port_access_mode(dev, id, false) + +/** + * dfl_fpga_cdev_config_ports_pf - configure ports to PF access mode + * + * @cdev: parent container device. + * + * This function is needed in sriov configuration routine. It could be used to + * configure the all released ports from VF access mode to PF. + */ +void dfl_fpga_cdev_config_ports_pf(struct dfl_fpga_cdev *cdev) +{ + struct dfl_feature_platform_data *pdata; + + mutex_lock(&cdev->lock); + list_for_each_entry(pdata, &cdev->port_dev_list, node) { + if (device_is_registered(&pdata->dev->dev)) + continue; + + config_port_pf_mode(cdev->fme_dev, pdata->id); + } + mutex_unlock(&cdev->lock); +} +EXPORT_SYMBOL_GPL(dfl_fpga_cdev_config_ports_pf); + +/** + * dfl_fpga_cdev_config_ports_vf - configure ports to VF access mode + * + * @cdev: parent container device. + * @num_vfs: VF device number. + * + * This function is needed in sriov configuration routine. It could be used to + * configure the released ports from PF access mode to VF. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_cdev_config_ports_vf(struct dfl_fpga_cdev *cdev, int num_vfs) +{ + struct dfl_feature_platform_data *pdata; + int ret = 0; + + mutex_lock(&cdev->lock); + /* + * can't turn multiple ports into 1 VF device, only 1 port for 1 VF + * device, so if released port number doesn't match VF device number, + * then reject the request with -EINVAL error code. + */ + if (cdev->released_port_num != num_vfs) { + ret = -EINVAL; + goto done; + } + + list_for_each_entry(pdata, &cdev->port_dev_list, node) { + if (device_is_registered(&pdata->dev->dev)) + continue; + + config_port_vf_mode(cdev->fme_dev, pdata->id); + } +done: + mutex_unlock(&cdev->lock); + return ret; +} +EXPORT_SYMBOL_GPL(dfl_fpga_cdev_config_ports_vf); + +static irqreturn_t dfl_irq_handler(int irq, void *arg) +{ + struct eventfd_ctx *trigger = arg; + + eventfd_signal(trigger, 1); + return IRQ_HANDLED; +} + +static int do_set_irq_trigger(struct dfl_feature *feature, unsigned int idx, + int fd) +{ + struct platform_device *pdev = feature->dev; + struct eventfd_ctx *trigger; + int irq, ret; + + irq = feature->irq_ctx[idx].irq; + + if (feature->irq_ctx[idx].trigger) { + free_irq(irq, feature->irq_ctx[idx].trigger); + kfree(feature->irq_ctx[idx].name); + eventfd_ctx_put(feature->irq_ctx[idx].trigger); + feature->irq_ctx[idx].trigger = NULL; + } + + if (fd < 0) + return 0; + + feature->irq_ctx[idx].name = + kasprintf(GFP_KERNEL, "fpga-irq[%u](%s-%x)", idx, + dev_name(&pdev->dev), feature->id); + if (!feature->irq_ctx[idx].name) + return -ENOMEM; + + trigger = eventfd_ctx_fdget(fd); + if (IS_ERR(trigger)) { + ret = PTR_ERR(trigger); + goto free_name; + } + + ret = request_irq(irq, dfl_irq_handler, 0, + feature->irq_ctx[idx].name, trigger); + if (!ret) { + feature->irq_ctx[idx].trigger = trigger; + return ret; + } + + eventfd_ctx_put(trigger); +free_name: + kfree(feature->irq_ctx[idx].name); + + return ret; +} + +/** + * dfl_fpga_set_irq_triggers - set eventfd triggers for dfl feature interrupts + * + * @feature: dfl sub feature. + * @start: start of irq index in this dfl sub feature. + * @count: number of irqs. + * @fds: eventfds to bind with irqs. unbind related irq if fds[n] is negative. + * unbind "count" specified number of irqs if fds ptr is NULL. + * + * Bind given eventfds with irqs in this dfl sub feature. Unbind related irq if + * fds[n] is negative. Unbind "count" specified number of irqs if fds ptr is + * NULL. + * + * Return: 0 on success, negative error code otherwise. + */ +int dfl_fpga_set_irq_triggers(struct dfl_feature *feature, unsigned int start, + unsigned int count, int32_t *fds) +{ + unsigned int i; + int ret = 0; + + /* overflow */ + if (unlikely(start + count < start)) + return -EINVAL; + + /* exceeds nr_irqs */ + if (start + count > feature->nr_irqs) + return -EINVAL; + + for (i = 0; i < count; i++) { + int fd = fds ? fds[i] : -1; + + ret = do_set_irq_trigger(feature, start + i, fd); + if (ret) { + while (i--) + do_set_irq_trigger(feature, start + i, -1); + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(dfl_fpga_set_irq_triggers); + +/** + * dfl_feature_ioctl_get_num_irqs - dfl feature _GET_IRQ_NUM ioctl interface. + * @pdev: the feature device which has the sub feature + * @feature: the dfl sub feature + * @arg: ioctl argument + * + * Return: 0 on success, negative error code otherwise. + */ +long dfl_feature_ioctl_get_num_irqs(struct platform_device *pdev, + struct dfl_feature *feature, + unsigned long arg) +{ + return put_user(feature->nr_irqs, (__u32 __user *)arg); +} +EXPORT_SYMBOL_GPL(dfl_feature_ioctl_get_num_irqs); + +/** + * dfl_feature_ioctl_set_irq - dfl feature _SET_IRQ ioctl interface. + * @pdev: the feature device which has the sub feature + * @feature: the dfl sub feature + * @arg: ioctl argument + * + * Return: 0 on success, negative error code otherwise. + */ +long dfl_feature_ioctl_set_irq(struct platform_device *pdev, + struct dfl_feature *feature, + unsigned long arg) +{ + struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct dfl_fpga_irq_set hdr; + s32 *fds; + long ret; + + if (!feature->nr_irqs) + return -ENOENT; + + if (copy_from_user(&hdr, (void __user *)arg, sizeof(hdr))) + return -EFAULT; + + if (!hdr.count || (hdr.start + hdr.count > feature->nr_irqs) || + (hdr.start + hdr.count < hdr.start)) + return -EINVAL; + + fds = memdup_user((void __user *)(arg + sizeof(hdr)), + array_size(hdr.count, sizeof(s32))); + if (IS_ERR(fds)) + return PTR_ERR(fds); + + mutex_lock(&pdata->lock); + ret = dfl_fpga_set_irq_triggers(feature, hdr.start, hdr.count, fds); + mutex_unlock(&pdata->lock); + + kfree(fds); + return ret; +} +EXPORT_SYMBOL_GPL(dfl_feature_ioctl_set_irq); static void __exit dfl_fpga_exit(void) { dfl_chardev_uinit(); dfl_ids_destroy(); + bus_unregister(&dfl_bus_type); } module_init(dfl_fpga_init); -- Gitblit v1.6.2