diff --git a/Documentation/ABI/testing/sysfs-bus-dfl b/Documentation/ABI/testing/sysfs-bus-dfl new file mode 100644 index 000000000000..23543be904f2 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-dfl @@ -0,0 +1,15 @@ +What: /sys/bus/dfl/devices/dfl_dev.X/type +Date: Aug 2020 +KernelVersion: 5.10 +Contact: Xu Yilun +Description: Read-only. It returns type of DFL FIU of the device. Now DFL + supports 2 FIU types, 0 for FME, 1 for PORT. + Format: 0x%x + +What: /sys/bus/dfl/devices/dfl_dev.X/feature_id +Date: Aug 2020 +KernelVersion: 5.10 +Contact: Xu Yilun +Description: Read-only. It returns feature identifier local to its DFL FIU + type. + Format: 0x%x diff --git a/MAINTAINERS b/MAINTAINERS index 3edf55b9954c..286fd7263982 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6828,14 +6828,17 @@ F: drivers/net/ethernet/nvidia/* FPGA DFL DRIVERS M: Wu Hao +R: Tom Rix L: linux-fpga@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-bus-dfl F: Documentation/fpga/dfl.rst F: drivers/fpga/dfl* F: include/uapi/linux/fpga-dfl.h FPGA MANAGER FRAMEWORK M: Moritz Fischer +R: Tom Rix L: linux-fpga@vger.kernel.org S: Maintained W: http://www.rocketboards.org diff --git a/drivers/fpga/dfl-fme-perf.c b/drivers/fpga/dfl-fme-perf.c index 6ce1ed222ea4..531266287eee 100644 --- a/drivers/fpga/dfl-fme-perf.c +++ b/drivers/fpga/dfl-fme-perf.c @@ -148,7 +148,7 @@ struct fme_perf_priv { struct device *dev; void __iomem *ioaddr; struct pmu pmu; - u64 id; + u16 id; u32 fab_users; u32 fab_port_id; diff --git a/drivers/fpga/dfl-pci.c b/drivers/fpga/dfl-pci.c index e220bec2927d..a2203d03c9e2 100644 --- a/drivers/fpga/dfl-pci.c +++ b/drivers/fpga/dfl-pci.c @@ -31,12 +31,12 @@ struct cci_drvdata { struct dfl_fpga_cdev *cdev; /* container device */ }; -static void __iomem *cci_pci_ioremap_bar(struct pci_dev *pcidev, int bar) +static void __iomem *cci_pci_ioremap_bar0(struct pci_dev *pcidev) { - if (pcim_iomap_regions(pcidev, BIT(bar), DRV_NAME)) + if (pcim_iomap_regions(pcidev, BIT(0), DRV_NAME)) return NULL; - return pcim_iomap_table(pcidev)[bar]; + return pcim_iomap_table(pcidev)[0]; } static int cci_pci_alloc_irq(struct pci_dev *pcidev) @@ -156,8 +156,8 @@ static int cci_enumerate_feature_devs(struct pci_dev *pcidev) goto irq_free_exit; } - /* start to find Device Feature List from Bar 0 */ - base = cci_pci_ioremap_bar(pcidev, 0); + /* start to find Device Feature List in Bar 0 */ + base = cci_pci_ioremap_bar0(pcidev); if (!base) { ret = -ENOMEM; goto irq_free_exit; @@ -172,7 +172,7 @@ static int cci_enumerate_feature_devs(struct pci_dev *pcidev) start = pci_resource_start(pcidev, 0); len = pci_resource_len(pcidev, 0); - dfl_fpga_enum_info_add_dfl(info, start, len, base); + dfl_fpga_enum_info_add_dfl(info, start, len); /* * find more Device Feature Lists (e.g. Ports) per information @@ -196,26 +196,24 @@ static int cci_enumerate_feature_devs(struct pci_dev *pcidev) */ bar = FIELD_GET(FME_PORT_OFST_BAR_ID, v); offset = FIELD_GET(FME_PORT_OFST_DFH_OFST, v); - base = cci_pci_ioremap_bar(pcidev, bar); - if (!base) - continue; - start = pci_resource_start(pcidev, bar) + offset; len = pci_resource_len(pcidev, bar) - offset; - dfl_fpga_enum_info_add_dfl(info, start, len, - base + offset); + dfl_fpga_enum_info_add_dfl(info, start, len); } } else if (dfl_feature_is_port(base)) { start = pci_resource_start(pcidev, 0); len = pci_resource_len(pcidev, 0); - dfl_fpga_enum_info_add_dfl(info, start, len, base); + dfl_fpga_enum_info_add_dfl(info, start, len); } else { ret = -ENODEV; goto irq_free_exit; } + /* release I/O mappings for next step enumeration */ + pcim_iounmap_regions(pcidev, BIT(0)); + /* start enumeration with prepared enumeration information */ cdev = dfl_fpga_feature_devs_enumerate(info); if (IS_ERR(cdev)) { diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index 649958a36e62..b450870b75ed 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -30,12 +30,6 @@ static DEFINE_MUTEX(dfl_id_mutex); * 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, @@ -58,7 +52,7 @@ static const char *dfl_pdata_key_strings[DFL_ID_MAX] = { */ struct dfl_dev_info { const char *name; - u32 dfh_id; + u16 dfh_id; struct idr id; enum dfl_fpga_devt_type devt_type; }; @@ -134,7 +128,7 @@ static enum dfl_id_type feature_dev_id_type(struct platform_device *pdev) 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; @@ -250,6 +244,249 @@ int dfl_fpga_check_port_id(struct platform_device *pdev, void *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 * @pdev: feature device. @@ -259,12 +496,15 @@ void dfl_fpga_dev_feature_uinit(struct platform_device *pdev) 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) { if (feature->ops->uinit) feature->ops->uinit(pdev, feature); feature->ops = NULL; } + } } EXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_uinit); @@ -273,8 +513,22 @@ static int dfl_feature_instance_init(struct platform_device *pdev, struct dfl_feature *feature, struct dfl_feature_driver *drv) { + void __iomem *base; int ret = 0; + 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) @@ -331,6 +585,10 @@ int dfl_fpga_dev_feature_init(struct platform_device *pdev, drv++; } + ret = dfl_devs_add(pdata); + if (ret) + goto exit; + return 0; exit: dfl_fpga_dev_feature_uinit(pdev); @@ -427,7 +685,9 @@ EXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_unregister); * @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. */ @@ -439,6 +699,8 @@ struct build_feature_devs_info { struct platform_device *feature_dev; void __iomem *ioaddr; + resource_size_t start; + resource_size_t len; struct list_head sub_features; int feature_num; }; @@ -454,7 +716,7 @@ struct build_feature_devs_info { * @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; @@ -484,10 +746,7 @@ static int build_info_commit_dev(struct build_feature_devs_info *binfo) 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)) @@ -530,16 +789,32 @@ static int build_info_commit_dev(struct build_feature_devs_info *binfo) /* 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, @@ -582,19 +857,13 @@ static int build_info_commit_dev(struct build_feature_devs_info *binfo) 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 * whether the id need to be reclaimed @@ -605,7 +874,7 @@ build_info_create_dev(struct build_feature_devs_info *binfo, 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); @@ -649,7 +918,7 @@ static inline u32 feature_size(void __iomem *start) 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); @@ -667,7 +936,7 @@ static u64 feature_id(void __iomem *start) } static int parse_feature_irqs(struct build_feature_devs_info *binfo, - resource_size_t ofst, u64 fid, + resource_size_t ofst, u16 fid, unsigned int *irq_base, unsigned int *nr_irqs) { void __iomem *base = binfo->ioaddr + ofst; @@ -713,12 +982,12 @@ static int parse_feature_irqs(struct build_feature_devs_info *binfo, return 0; } - dev_dbg(binfo->dev, "feature: 0x%llx, irq_base: %u, nr_irqs: %u\n", + 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%llx\n", fid); + "Invalid interrupt number in feature 0x%x\n", fid); return -EINVAL; } @@ -726,7 +995,7 @@ static int parse_feature_irqs(struct build_feature_devs_info *binfo, virq = binfo->irq_table[ibase + i]; if (virq < 0 || virq > NR_IRQS) { dev_err(binfo->dev, - "Invalid irq table entry for feature 0x%llx\n", + "Invalid irq table entry for feature 0x%x\n", fid); return -EINVAL; } @@ -747,18 +1016,17 @@ static int parse_feature_irqs(struct build_feature_devs_info *binfo, */ 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); @@ -770,12 +1038,11 @@ create_feature_instance(struct build_feature_devs_info *binfo, 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->irq_base = irq_base; finfo->nr_irqs = nr_irqs; - finfo->ioaddr = dfl->ioaddr + ofst; list_add_tail(&finfo->node, &binfo->sub_features); binfo->feature_num++; @@ -784,7 +1051,6 @@ create_feature_instance(struct build_feature_devs_info *binfo, } 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); @@ -792,21 +1058,22 @@ static int parse_feature_port_afu(struct build_feature_devs_info *binfo, 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); @@ -815,35 +1082,79 @@ static int parse_feature_afu(struct build_feature_devs_info *binfo, 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); @@ -851,41 +1162,39 @@ static int parse_feature_fiu(struct build_feature_devs_info *binfo, } 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); @@ -895,14 +1204,17 @@ static int parse_feature(struct build_feature_devs_info *binfo, } 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) { if (end - start < DFH_SIZE) { @@ -910,11 +1222,11 @@ static int parse_feature_list(struct build_feature_devs_info *binfo, 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 */ @@ -923,7 +1235,12 @@ static int parse_feature_list(struct build_feature_devs_info *binfo, } /* 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) @@ -976,7 +1293,6 @@ EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_free); * @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 @@ -985,8 +1301,7 @@ EXPORT_SYMBOL_GPL(dfl_fpga_enum_info_free); * 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; @@ -996,7 +1311,6 @@ int dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, dfl->start = start; dfl->len = len; - dfl->ioaddr = ioaddr; list_add_tail(&dfl->node, &info->dfls); @@ -1119,7 +1433,7 @@ dfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info) * 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); @@ -1212,11 +1526,17 @@ static int __init dfl_fpga_init(void) { 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; } @@ -1424,7 +1744,7 @@ static int do_set_irq_trigger(struct dfl_feature *feature, unsigned int idx, return 0; feature->irq_ctx[idx].name = - kasprintf(GFP_KERNEL, "fpga-irq[%u](%s-%llx)", idx, + kasprintf(GFP_KERNEL, "fpga-irq[%u](%s-%x)", idx, dev_name(&pdev->dev), feature->id); if (!feature->irq_ctx[idx].name) return -ENOMEM; @@ -1554,6 +1874,7 @@ static void __exit dfl_fpga_exit(void) { dfl_chardev_uinit(); dfl_ids_destroy(); + bus_unregister(&dfl_bus_type); } module_init(dfl_fpga_init); diff --git a/drivers/fpga/dfl.h b/drivers/fpga/dfl.h index a32dfba2a88b..5dc758f655b7 100644 --- a/drivers/fpga/dfl.h +++ b/drivers/fpga/dfl.h @@ -197,7 +197,7 @@ int dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id); * @id: unique dfl private feature id. */ struct dfl_feature_id { - u64 id; + u16 id; }; /** @@ -236,16 +236,18 @@ struct dfl_feature_irq_ctx { * @irq_ctx: interrupt context list. * @nr_irqs: number of interrupt contexts. * @ops: ops of this sub feature. + * @ddev: ptr to the dfl device of this sub feature. * @priv: priv data of this feature. */ struct dfl_feature { struct platform_device *dev; - u64 id; + u16 id; int resource_index; void __iomem *ioaddr; struct dfl_feature_irq_ctx *irq_ctx; unsigned int nr_irqs; const struct dfl_feature_ops *ops; + struct dfl_device *ddev; void *priv; }; @@ -365,7 +367,7 @@ struct platform_device *dfl_fpga_inode_to_feature_dev(struct inode *inode) (feature) < (pdata)->features + (pdata)->num; (feature)++) static inline -struct dfl_feature *dfl_get_feature_by_id(struct device *dev, u64 id) +struct dfl_feature *dfl_get_feature_by_id(struct device *dev, u16 id) { struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); struct dfl_feature *feature; @@ -378,7 +380,7 @@ struct dfl_feature *dfl_get_feature_by_id(struct device *dev, u64 id) } static inline -void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u64 id) +void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u16 id) { struct dfl_feature *feature = dfl_get_feature_by_id(dev, id); @@ -389,7 +391,7 @@ void __iomem *dfl_get_feature_ioaddr_by_id(struct device *dev, u64 id) return NULL; } -static inline bool is_dfl_feature_present(struct device *dev, u64 id) +static inline bool is_dfl_feature_present(struct device *dev, u16 id) { return !!dfl_get_feature_ioaddr_by_id(dev, id); } @@ -441,22 +443,17 @@ struct dfl_fpga_enum_info { * * @start: base address of this device feature list. * @len: size of this device feature list. - * @ioaddr: mapped base address of this device feature list. * @node: node in list of device feature lists. */ struct dfl_fpga_enum_dfl { resource_size_t start; resource_size_t len; - - void __iomem *ioaddr; - struct list_head node; }; struct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev); 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); int dfl_fpga_enum_info_add_irq(struct dfl_fpga_enum_info *info, unsigned int nr_irqs, int *irq_table); void dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info); @@ -519,4 +516,88 @@ long dfl_feature_ioctl_set_irq(struct platform_device *pdev, struct dfl_feature *feature, unsigned long arg); +/** + * enum dfl_id_type - define the DFL FIU types + */ +enum dfl_id_type { + FME_ID, + PORT_ID, + DFL_ID_MAX, +}; + +/** + * struct dfl_device_id - dfl device identifier + * @type: contains 4 bits DFL FIU type of the device. See enum dfl_id_type. + * @feature_id: contains 12 bits feature identifier local to its DFL FIU type. + * @driver_data: driver specific data. + */ +struct dfl_device_id { + u8 type; + u16 feature_id; + unsigned long driver_data; +}; + +/** + * struct dfl_device - represent an dfl device on dfl bus + * + * @dev: generic device interface. + * @id: id of the dfl device. + * @type: type of DFL FIU of the device. See enum dfl_id_type. + * @feature_id: 16 bits feature identifier local to its DFL FIU type. + * @mmio_res: mmio resource of this dfl device. + * @irqs: list of Linux IRQ numbers of this dfl device. + * @num_irqs: number of IRQs supported by this dfl device. + * @cdev: pointer to DFL FPGA container device this dfl device belongs to. + * @id_entry: matched id entry in dfl driver's id table. + */ +struct dfl_device { + struct device dev; + int id; + u8 type; + u16 feature_id; + struct resource mmio_res; + int *irqs; + unsigned int num_irqs; + struct dfl_fpga_cdev *cdev; + const struct dfl_device_id *id_entry; +}; + +/** + * struct dfl_driver - represent an dfl device driver + * + * @drv: driver model structure. + * @id_table: pointer to table of device IDs the driver is interested in. + * { } member terminated. + * @probe: mandatory callback for device binding. + * @remove: callback for device unbinding. + */ +struct dfl_driver { + struct device_driver drv; + const struct dfl_device_id *id_table; + + int (*probe)(struct dfl_device *dfl_dev); + void (*remove)(struct dfl_device *dfl_dev); +}; + +#define to_dfl_dev(d) container_of(d, struct dfl_device, dev) +#define to_dfl_drv(d) container_of(d, struct dfl_driver, drv) + +/* + * use a macro to avoid include chaining to get THIS_MODULE. + */ +#define dfl_driver_register(drv) \ + __dfl_driver_register(drv, THIS_MODULE) +int __dfl_driver_register(struct dfl_driver *dfl_drv, struct module *owner); +void dfl_driver_unregister(struct dfl_driver *dfl_drv); + +/* + * module_dfl_driver() - Helper macro for drivers that don't do + * anything special in module init/exit. This eliminates a lot of + * boilerplate. Each module may only use this macro once, and + * calling it replaces module_init() and module_exit(). + */ +#define module_dfl_driver(__dfl_driver) \ + module_driver(__dfl_driver, dfl_driver_register, \ + dfl_driver_unregister) + #endif /* __FPGA_DFL_H */ diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index bde5a9d460c5..c3134b89c3fe 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * FPGA Region - Device Tree support for FPGA programming under Linux + * FPGA Region - Support for FPGA programming under Linux * * Copyright (C) 2013-2016 Altera Corporation * Copyright (C) 2017 Intel Corporation diff --git a/drivers/fpga/stratix10-soc.c b/drivers/fpga/stratix10-soc.c index 44b7c569d4dc..657a70c5fc99 100644 --- a/drivers/fpga/stratix10-soc.c +++ b/drivers/fpga/stratix10-soc.c @@ -196,17 +196,13 @@ static int s10_ops_write_init(struct fpga_manager *mgr, if (ret < 0) goto init_done; - ret = wait_for_completion_interruptible_timeout( + ret = wait_for_completion_timeout( &priv->status_return_completion, S10_RECONFIG_TIMEOUT); if (!ret) { dev_err(dev, "timeout waiting for RECONFIG_REQUEST\n"); ret = -ETIMEDOUT; goto init_done; } - if (ret < 0) { - dev_err(dev, "error (%d) waiting for RECONFIG_REQUEST\n", ret); - goto init_done; - } ret = 0; if (!test_and_clear_bit(SVC_STATUS_OK, &priv->status)) { @@ -318,7 +314,7 @@ static int s10_ops_write(struct fpga_manager *mgr, const char *buf, */ wait_status = 1; /* not timed out */ if (!priv->status) - wait_status = wait_for_completion_interruptible_timeout( + wait_status = wait_for_completion_timeout( &priv->status_return_completion, S10_BUFFER_TIMEOUT); @@ -340,13 +336,6 @@ static int s10_ops_write(struct fpga_manager *mgr, const char *buf, ret = -ETIMEDOUT; break; } - if (wait_status < 0) { - ret = wait_status; - dev_err(dev, - "error (%d) waiting for svc layer buffers\n", - ret); - break; - } } if (!s10_free_buffers(mgr)) @@ -372,7 +361,7 @@ static int s10_ops_write_complete(struct fpga_manager *mgr, if (ret < 0) break; - ret = wait_for_completion_interruptible_timeout( + ret = wait_for_completion_timeout( &priv->status_return_completion, timeout); if (!ret) { dev_err(dev, @@ -380,12 +369,6 @@ static int s10_ops_write_complete(struct fpga_manager *mgr, ret = -ETIMEDOUT; break; } - if (ret < 0) { - dev_err(dev, - "error (%d) waiting for RECONFIG_COMPLETED\n", - ret); - break; - } /* Not error or timeout, so ret is # of jiffies until timeout */ timeout = ret; ret = 0; diff --git a/drivers/fpga/xilinx-spi.c b/drivers/fpga/xilinx-spi.c index 2967aa2a74e2..824abbbd631e 100644 --- a/drivers/fpga/xilinx-spi.c +++ b/drivers/fpga/xilinx-spi.c @@ -27,11 +27,22 @@ struct xilinx_spi_conf { struct gpio_desc *done; }; -static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr) +static int get_done_gpio(struct fpga_manager *mgr) { struct xilinx_spi_conf *conf = mgr->priv; + int ret; - if (!gpiod_get_value(conf->done)) + ret = gpiod_get_value(conf->done); + + if (ret < 0) + dev_err(&mgr->dev, "Error reading DONE (%d)\n", ret); + + return ret; +} + +static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr) +{ + if (!get_done_gpio(mgr)) return FPGA_MGR_STATE_RESET; return FPGA_MGR_STATE_UNKNOWN; @@ -57,11 +68,21 @@ static int wait_for_init_b(struct fpga_manager *mgr, int value, if (conf->init_b) { while (time_before(jiffies, timeout)) { - /* dump_state(conf, "wait for init_d .."); */ - if (gpiod_get_value(conf->init_b) == value) + int ret = gpiod_get_value(conf->init_b); + + if (ret == value) return 0; + + if (ret < 0) { + dev_err(&mgr->dev, "Error reading INIT_B (%d)\n", ret); + return ret; + } + usleep_range(100, 400); } + + dev_err(&mgr->dev, "Timeout waiting for INIT_B to %s\n", + value ? "assert" : "deassert"); return -ETIMEDOUT; } @@ -78,7 +99,7 @@ static int xilinx_spi_write_init(struct fpga_manager *mgr, int err; if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { - dev_err(&mgr->dev, "Partial reconfiguration not supported.\n"); + dev_err(&mgr->dev, "Partial reconfiguration not supported\n"); return -EINVAL; } @@ -86,7 +107,6 @@ static int xilinx_spi_write_init(struct fpga_manager *mgr, err = wait_for_init_b(mgr, 1, 1); /* min is 500 ns */ if (err) { - dev_err(&mgr->dev, "INIT_B pin did not go low\n"); gpiod_set_value(conf->prog_b, 0); return err; } @@ -94,12 +114,10 @@ static int xilinx_spi_write_init(struct fpga_manager *mgr, gpiod_set_value(conf->prog_b, 0); err = wait_for_init_b(mgr, 0, 0); - if (err) { - dev_err(&mgr->dev, "INIT_B pin did not go high\n"); + if (err) return err; - } - if (gpiod_get_value(conf->done)) { + if (get_done_gpio(mgr)) { dev_err(&mgr->dev, "Unexpected DONE pin state...\n"); return -EIO; } @@ -152,25 +170,46 @@ static int xilinx_spi_write_complete(struct fpga_manager *mgr, struct fpga_image_info *info) { struct xilinx_spi_conf *conf = mgr->priv; - unsigned long timeout; + unsigned long timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us); + bool expired = false; + int done; int ret; - if (gpiod_get_value(conf->done)) - return xilinx_spi_apply_cclk_cycles(conf); + /* + * This loop is carefully written such that if the driver is + * scheduled out for more than 'timeout', we still check for DONE + * before giving up and we apply 8 extra CCLK cycles in all cases. + */ + while (!expired) { + expired = time_after(jiffies, timeout); - timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us); - - while (time_before(jiffies, timeout)) { + done = get_done_gpio(mgr); + if (done < 0) + return done; ret = xilinx_spi_apply_cclk_cycles(conf); if (ret) return ret; - if (gpiod_get_value(conf->done)) - return xilinx_spi_apply_cclk_cycles(conf); + if (done) + return 0; + } + + if (conf->init_b) { + ret = gpiod_get_value(conf->init_b); + + if (ret < 0) { + dev_err(&mgr->dev, "Error reading INIT_B (%d)\n", ret); + return ret; + } + + dev_err(&mgr->dev, + ret ? "CRC error or invalid device\n" + : "Missing sync word or incomplete bitstream\n"); + } else { + dev_err(&mgr->dev, "Timeout after config data transfer\n"); } - dev_err(&mgr->dev, "Timeout after config data transfer.\n"); return -ETIMEDOUT; }