All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marco Pagani <marco.pagani@linux.dev>
To: Moritz Fischer <mdf@kernel.org>, Xu Yilun <yilun.xu@intel.com>,
	Tom Rix <trix@redhat.com>
Cc: Marco Pagani <marco.pagani@linux.dev>,
	linux-fpga@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [RFC PATCH fpga/for-next 2/2] fpga: of-fpga-region: Add support for region variants
Date: Mon,  8 Jun 2026 18:42:47 +0200	[thread overview]
Message-ID: <20260608164247.1998417-3-marco.pagani@linux.dev> (raw)
In-Reply-To: <20260608164247.1998417-1-marco.pagani@linux.dev>

Extend of-fpga-region with support for FPGA region variants statically
defined in the device tree.

During probing, the code now looks for an fpga-variants container
node. If present, it parses each variant child node and registers it.
The variant that is programmed in the region at boot time is defined
by the base-variant property. Additionally, the firmware-cached
property allows caching bitstreams in memory, enabling a fast
reconfiguration path for real-time (latency-sensitive) applications.

Signed-off-by: Marco Pagani <marco.pagani@linux.dev>
---
 drivers/fpga/of-fpga-region.c | 272 +++++++++++++++++++++++++++++++++-
 1 file changed, 270 insertions(+), 2 deletions(-)

diff --git a/drivers/fpga/of-fpga-region.c b/drivers/fpga/of-fpga-region.c
index 9107a5b461d3..ea32775b55ce 100644
--- a/drivers/fpga/of-fpga-region.c
+++ b/drivers/fpga/of-fpga-region.c
@@ -8,6 +8,7 @@
 #include <linux/fpga/fpga-bridge.h>
 #include <linux/fpga/fpga-mgr.h>
 #include <linux/fpga/fpga-region.h>
+#include <linux/firmware.h>
 #include <linux/idr.h>
 #include <linux/kernel.h>
 #include <linux/list.h>
@@ -18,6 +19,18 @@
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 
+/**
+ * struct of_fpga_variant_priv - private data for FPGA Region variants
+ * @fw_name: name of the firmware file containing the FPGA image
+ * @fw_buf: buffer containing the cached firmware image
+ * @fw_size: size in bytes of the buffer containing the cached firmware image
+ */
+struct of_fpga_variant_priv {
+	char *fw_name;
+	void *fw_buf;
+	size_t fw_size;
+};
+
 static const struct of_device_id fpga_region_of_match[] = {
 	{ .compatible = "fpga-region", },
 	{},
@@ -140,6 +153,168 @@ static int of_fpga_region_get_bridges(struct fpga_region *region)
 	return 0;
 }
 
+static int of_fpga_remove_variant_child(struct device *dev, void *data)
+{
+	struct device_node *variant_np = data;
+
+	if (dev->of_node && dev->of_node->parent == variant_np) {
+		if (dev_is_platform(dev)) {
+			of_node_clear_flag(dev->of_node, OF_POPULATED);
+			platform_device_unregister(to_platform_device(dev));
+		}
+	}
+	return 0;
+}
+
+static int of_fpga_region_populate_variant(struct device *dev,
+					   struct device_node *variant_np)
+{
+	struct device_node *child_np;
+
+	for_each_available_child_of_node(variant_np, child_np) {
+		if (!of_platform_device_create(child_np, NULL, dev))
+			dev_warn(dev, "failed to create device node.\n");
+	}
+
+	return 0;
+}
+
+/**
+ * of_fpga_region_apply_variant - apply a specific FPGA variant to the region
+ * @region: FPGA region to be programmed
+ * @variant: FPGA variant requested to be applied
+ *
+ * Retrieves the specific variant node from the fpga-variants container,
+ * handles firmware loading (including lazy caching if firmware-cached is
+ * set), programs the FPGA region with the variant, and finally populates
+ * the platform devices for the child IPs.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+static int of_fpga_region_apply_variant(struct fpga_region *region,
+					struct fpga_variant *variant)
+{
+	struct of_fpga_variant_priv *priv = variant->priv;
+	struct device *dev = &region->dev;
+	struct device_node *variants_np, *variant_np;
+	const struct firmware *fw;
+	struct fpga_image_info *info;
+	int ret;
+
+	variants_np = of_get_child_by_name(dev->of_node, "fpga-variants");
+	if (!variants_np)
+		return -ENODEV;
+
+	variant_np = of_get_child_by_name(variants_np, variant->name);
+	of_node_put(variants_np);
+	if (!variant_np)
+		return -ENODEV;
+
+	info = fpga_image_info_alloc(dev);
+	if (!info) {
+		ret = -ENOMEM;
+		goto err_put_node;
+	}
+
+	if (!of_property_read_bool(variant_np, "firmware-cached")) {
+		info->firmware_name = devm_kstrdup(dev, priv->fw_name, GFP_KERNEL);
+		if (!info->firmware_name) {
+			ret = -ENOMEM;
+			goto err_free_info;
+		}
+	} else if (priv->fw_buf) {
+		info->buf = priv->fw_buf;
+		info->count = priv->fw_size;
+	} else {
+		ret = request_firmware(&fw, priv->fw_name, dev);
+		if (ret) {
+			dev_err(dev, "failed to request firmware '%s'\n", priv->fw_name);
+			goto err_free_info;
+		}
+
+		priv->fw_buf = devm_kmemdup(dev, fw->data, fw->size, GFP_KERNEL);
+		priv->fw_size = fw->size;
+		release_firmware(fw);
+
+		if (!priv->fw_buf) {
+			ret = -ENOMEM;
+			goto err_free_info;
+		}
+
+		info->buf = priv->fw_buf;
+		info->count = priv->fw_size;
+	}
+
+	/* Checked during probing */
+	info->flags |= FPGA_MGR_PARTIAL_RECONFIG;
+
+	of_property_read_u32(dev->of_node, "region-unfreeze-timeout-us",
+			     &info->enable_timeout_us);
+	of_property_read_u32(dev->of_node, "region-freeze-timeout-us",
+			     &info->disable_timeout_us);
+	of_property_read_u32(dev->of_node, "config-complete-timeout-us",
+			     &info->config_complete_timeout_us);
+
+	region->info = info;
+
+	if (info->firmware_name || info->buf) {
+		ret = fpga_region_program_fpga(region);
+		if (ret) {
+			dev_err(dev, "failed to program FPGA\n");
+			goto err_clear_info;
+		}
+	}
+
+	ret = of_fpga_region_populate_variant(dev, variant_np);
+	if (ret) {
+		dev_err(dev, "failed to populate variant IP nodes\n");
+		goto err_clear_info;
+	}
+
+	of_node_put(variant_np);
+	return 0;
+
+err_clear_info:
+	region->info = NULL;
+err_free_info:
+	fpga_image_info_free(info);
+err_put_node:
+	of_node_put(variant_np);
+	return ret;
+}
+
+/**
+ * of_fpga_region_remove_variant - remove the current FPGA variant from the region
+ * @region: FPGA region
+ * @variant: FPGA variant being removed
+ *
+ * Unregisters all devices that were populared for the variant's IPs
+ * during apply_variant() and frees the FPGA image.
+ */
+static void of_fpga_region_remove_variant(struct fpga_region *region,
+					  struct fpga_variant *variant)
+{
+	struct device_node *variants_np, *variant_np;
+	struct device *dev = &region->dev;
+
+	variants_np = of_get_child_by_name(dev->of_node, "fpga-variants");
+	if (variants_np) {
+		variant_np = of_get_child_by_name(variants_np, variant->name);
+		of_node_put(variants_np);
+
+		if (variant_np) {
+			device_for_each_child(dev, variant_np,
+					      of_fpga_remove_variant_child);
+			of_node_put(variant_np);
+		}
+	}
+
+	if (region->info) {
+		fpga_image_info_free(region->info);
+		region->info = NULL;
+	}
+}
+
 /**
  * child_regions_with_firmware - Used to check the child region info.
  * @overlay: device node of the overlay
@@ -397,8 +572,16 @@ static int of_fpga_region_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct device_node *np = dev->of_node;
+	struct device_node *var_np, *base_np = NULL, *variants_np = NULL;
 	struct fpga_region *region;
 	struct fpga_manager *mgr;
+	struct fpga_region_info info = { 0 };
+	struct of_fpga_variant_priv *priv;
+	const char *base_variant_name = NULL;
+	const char *fw_name = NULL;
+	bool has_base_variant = false;
+	bool base_variant_found = false;
+	bool is_base = false;
 	int ret;
 
 	/* Find the FPGA mgr specified by region or parent region. */
@@ -406,20 +589,105 @@ static int of_fpga_region_probe(struct platform_device *pdev)
 	if (IS_ERR(mgr))
 		return -EPROBE_DEFER;
 
-	region = fpga_region_register(dev, mgr, of_fpga_region_get_bridges);
+	/* Register variants support only if the device tree is well-formed */
+	if (!of_property_read_string(np, "base-variant", &base_variant_name))
+		has_base_variant = true;
+
+	if (has_base_variant) {
+		if (!of_property_read_bool(np, "partial-fpga-config")) {
+			dev_err(dev, "Region '%s' is missing 'partial-fpga-config' property.\n",
+				np->name);
+			ret = -EINVAL;
+			goto eprobe_mgr_put;
+		}
+
+		variants_np = of_get_child_by_name(np, "fpga-variants");
+		if (!variants_np) {
+			dev_err(dev, "Missing 'fpga-variants' container node.\n");
+			ret = -EINVAL;
+			goto eprobe_mgr_put;
+		}
+
+		for_each_available_child_of_node(variants_np, var_np) {
+			if (!of_property_present(var_np, "firmware-name")) {
+				dev_err(dev, "Variant '%s' is missing 'firmware-name'.\n",
+					var_np->name);
+				of_node_put(var_np);
+				ret = -EINVAL;
+				goto eprobe_mgr_put;
+			}
+
+			if (!strcmp(var_np->name, base_variant_name))
+				base_variant_found = true;
+		}
+
+		if (!base_variant_found) {
+			dev_err(dev, "base-variant '%s' does not match.\n",
+				base_variant_name);
+			ret = -EINVAL;
+			goto eprobe_mgr_put;
+		}
+
+		info.apply_variant = of_fpga_region_apply_variant;
+		info.remove_variant = of_fpga_region_remove_variant;
+	}
+
+	info.mgr = mgr;
+	info.get_bridges = of_fpga_region_get_bridges;
+
+	region = fpga_region_register_full(dev, &info);
 	if (IS_ERR(region)) {
 		ret = PTR_ERR(region);
 		goto eprobe_mgr_put;
 	}
+	if (has_base_variant && variants_np) {
+		for_each_available_child_of_node(variants_np, var_np) {
+			priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+			if (!priv) {
+				of_node_put(var_np);
+				ret = -ENOMEM;
+				goto eprobe_region_unregister;
+			}
+
+			of_property_read_string(var_np, "firmware-name", &fw_name);
+			priv->fw_name = devm_kstrdup(dev, fw_name, GFP_KERNEL);
+			if (!priv->fw_name) {
+				of_node_put(var_np);
+				ret = -ENOMEM;
+				goto eprobe_region_unregister;
+			}
+
+			is_base = !strcmp(var_np->name, base_variant_name);
+			if (is_base)
+				base_np = of_node_get(var_np);
+
+			ret = fpga_region_add_variant(region, var_np->name, is_base, priv);
+			if (ret) {
+				dev_err(dev, "Cannot add variant '%s'.\n", var_np->name);
+				of_node_put(var_np);
+				goto eprobe_region_unregister;
+			}
+		}
+		of_node_put(variants_np);
+	}
 
 	of_platform_populate(np, fpga_region_of_match, NULL, &region->dev);
-	platform_set_drvdata(pdev, region);
 
+	if (base_np) {
+		of_fpga_region_populate_variant(&region->dev, base_np);
+		of_node_put(base_np);
+	}
+
+	platform_set_drvdata(pdev, region);
 	dev_info(dev, "FPGA Region probed\n");
 
 	return 0;
 
+eprobe_region_unregister:
+	fpga_region_unregister(region);
 eprobe_mgr_put:
+	of_node_put(base_np);
+	of_node_put(variants_np);
 	fpga_mgr_put(mgr);
 	return ret;
 }
-- 
2.54.0


  parent reply	other threads:[~2026-06-08 16:43 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-08 16:42 [RFC PATCH] fpga: region: Add support for FPGA region variants Marco Pagani
2026-06-08 16:42 ` [RFC PATCH fpga/for-next 1/2] " Marco Pagani
2026-06-08 16:42 ` Marco Pagani [this message]
2026-06-26 12:58 ` [RFC PATCH] " Xu Yilun
2026-06-30  9:39   ` Marco Pagani
2026-06-30 14:58     ` Xu Yilun
2026-07-06 10:05       ` Marco Pagani
2026-07-07  8:02         ` Xu Yilun
2026-07-07 21:49           ` Marco Pagani

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260608164247.1998417-3-marco.pagani@linux.dev \
    --to=marco.pagani@linux.dev \
    --cc=linux-fpga@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mdf@kernel.org \
    --cc=trix@redhat.com \
    --cc=yilun.xu@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.