[RFC,5/5] fpga-region support for fdt headers on fpga images
diff mbox

Message ID 1500925755-5727-6-git-send-email-atull@kernel.org
State New
Headers show

Commit Message

Alan Tull July 24, 2017, 7:49 p.m. UTC
Support a FDT header for FPGA images.  Header format is a simplified
FIT containing at most one FPGA image and one DT overlay.

The FPGA image section includes FPGA image info properties needed for
programming.

If present, the DT overlay image is applied after the FPGA is
programmed.

Both images are optional.  If the DT overlay isn't present, this
interface may be used on systems running without a live Device Tree.

todo: add scatter gather table support.

Signed-off-by: Alan Tull <atull@kernel.org>
---
 drivers/fpga/Kconfig             |   7 +
 drivers/fpga/fpga-region.c       | 440 +++++++++++++++++++++++++++++++++++++++
 drivers/fpga/of-fpga-region.c    |   7 +
 include/linux/fpga/fpga-mgr.h    |   9 +
 include/linux/fpga/fpga-region.h |   1 +
 5 files changed, 464 insertions(+)

Patch
diff mbox

diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index c349c42..f4b595b 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -19,6 +19,13 @@  config FPGA_REGION
 	  and the FPGA Bridges associated with either a reconfigurable
 	  region of an FPGA or a whole FPGA.
 
+config FPGA_REGION_SYSFS
+       bool "FPGA Region Sysfs"
+	depends on FPGA_REGION
+	help
+	  FPGA Region sysfs interface.  This creates sysfs files under
+	  /sys/class/fpga_region/ to control programming FPGA regions.
+
 config OF_FPGA_REGION
 	tristate "FPGA Region Device Tree Overlay Support"
 	depends on OF && FPGA_REGION
diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c
index 944d24a..a9dd61e 100644
--- a/drivers/fpga/fpga-region.c
+++ b/drivers/fpga/fpga-region.c
@@ -4,6 +4,15 @@ 
  *  Copyright (C) 2013-2016 Altera Corporation
  *  Copyright (C) 2017 Intel Corporation
  *
+ * Dynamic DT code borrowed from Pantelis Antoniou's
+ * "OF: DT-Overlay configfs interface (v7)" patch.
+ * Copyright (C) 2013 - Pantelis Antoniou <panto@antoniou-consulting.com>
+ *
+ * FIT image parsing adapted from u-boot
+ * (C) Copyright 2008 Semihalf
+ * (C) Copyright 2000-2006
+ * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+ *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms and conditions of the GNU General Public License,
  * version 2, as published by the Free Software Foundation.
@@ -17,6 +26,7 @@ 
  * this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/firmware.h>
 #include <linux/fpga/fpga-bridge.h>
 #include <linux/fpga/fpga-mgr.h>
 #include <linux/fpga/fpga-region.h>
@@ -24,6 +34,9 @@ 
 #include <linux/kernel.h>
 #include <linux/list.h>
 #include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_fdt.h>
+#include <linux/libfdt.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 
@@ -91,6 +104,312 @@  static void fpga_region_put(struct fpga_region *region)
 	mutex_unlock(&region->mutex);
 }
 
+/*
+ * todo - of_fpga_region_overlay_apply() is adapted from create_overlay()
+ * (some changed structs) from Pantelis Antoniou's
+ * "OF: DT-Overlay configfs interface (v7)" patch.
+ * that hasn't been accepted upstream yet.
+ */
+static int of_fpga_region_overlay_apply(struct fpga_region *region)
+{
+	struct device *dev = &region->dev;
+	struct fpga_image_info *info = region->info;
+	int ret;
+
+	WARN_ON(info->overlay);
+
+	of_fdt_unflatten_tree((void *)info->fdt_blob, NULL,
+			      &info->overlay);
+	if (!info->overlay) {
+		dev_err(dev, "%s: failed to unflatten tree\n", __func__);
+		ret = -EINVAL;
+		return ret;
+	}
+	pr_debug("%s: unflattened OK\n", __func__);
+
+	/* mark it as detached */
+	of_node_set_flag(info->overlay, OF_DETACHED);
+
+	/* FDT in bitstream should not specify an external FPGA image to load */
+	if (of_find_property(info->overlay, "firmware-name", NULL))
+		dev_warn(dev, "Ignoring firmware-name property in FPGA image header\n");
+
+	/* perform resolution */
+	ret = of_resolve_phandles(info->overlay);
+	if (ret != 0) {
+		dev_err(dev, "%s: Failed to resolve tree\n", __func__);
+		return ret;
+	}
+	pr_debug("%s: resolved OK\n", __func__);
+
+	ret = of_overlay_create(info->overlay);
+	if (ret < 0) {
+		dev_err(dev, "%s: Failed to create overlay (err=%d)\n",
+			__func__, ret);
+		return ret;
+	}
+	info->ov_id = ret;
+
+	return 0;
+}
+
+static void fpga_region_overlay_rm(struct fpga_region *region)
+{
+	struct device *dev = &region->dev;
+	struct fpga_image_info *info = region->info;
+
+	if (info->ov_id >= 0) {
+		of_overlay_destroy(info->ov_id);
+		info->ov_id = -1;
+	}
+	if (info->fdt_blob) {
+		devm_kfree(dev, info->fdt_blob);
+		info->fdt_blob = NULL;
+	}
+}
+
+/**
+ * get_data_prop - get pointer, length of binary data
+ *
+ * FDT is in info->header.  Find the data property at the node offset.
+ * Give a pointer to the binary data.
+ *
+ * Returns length of data or zero
+ */
+static int get_data_prop(struct fpga_image_info *info,
+			 int noffset, const void **data)
+{
+	int len;
+
+	*data = fdt_getprop(info->header, noffset, "data", &len);
+	if (!*data)
+		return 0;
+
+	return len;
+}
+
+static int get_int_prop(struct fpga_image_info *info,
+			int root_offset,
+			const char *prop_str)
+{
+	const u32 *prop;
+
+	prop = fdt_getprop(info->header, root_offset, prop_str, NULL);
+	if (prop)
+		return fdt32_to_cpu(*prop);
+
+	return 0;
+}
+
+static void get_bool_prop_to_flags(struct fpga_image_info *info,
+				   int root_offset,
+				   const char *prop_str, int bit)
+{
+	const u32 *prop;
+
+	prop = fdt_getprop(info->header, root_offset, prop_str, NULL);
+	if (prop)
+		info->flags |= bit;
+}
+
+/**
+ * region_parse_section_fpga - parse fpga image section
+ *
+ * FPGA image header is in a contiguous buffer (region->info->header).
+ * Parse the image info.  Get pointers to the raw image.
+ *
+ * @region: FPGA region
+ * @node_offset: offset to the flat_dt node
+ *
+ * Return: 0 for success or negative error code.
+ */
+static int region_parse_section_fpga(struct fpga_region *region,
+				     int node_offset)
+{
+	struct device *dev = &region->dev;
+	struct fpga_image_info *info = region->info;
+	const void *raw_image;
+	size_t image_size;
+
+	if (fdt_getprop(info->header, node_offset, "firmware-name", NULL)) {
+		dev_warn(dev,
+			 "Ignoring firmware-name property in FPGA image header\n");
+	}
+
+	get_bool_prop_to_flags(info, node_offset, "partial-fpga-config",
+			       FPGA_MGR_PARTIAL_RECONFIG);
+
+	get_bool_prop_to_flags(info, node_offset, "external-fpga-config",
+			       FPGA_MGR_EXTERNAL_CONFIG);
+
+	get_bool_prop_to_flags(info, node_offset, "encrypted-fpga-config",
+			       FPGA_MGR_ENCRYPTED_BITSTREAM);
+
+	info->enable_timeout_us =
+		get_int_prop(info, node_offset, "region-unfreeze-timeout-us");
+
+	info->disable_timeout_us =
+		get_int_prop(info, node_offset, "region-freeze-timeout-us");
+
+	info->config_complete_timeout_us =
+		get_int_prop(info, node_offset, "config-complete-timeout-us");
+
+	image_size = get_data_prop(info, node_offset, &raw_image);
+	if (!image_size)
+		return 0;
+
+	if (info->sgt) {
+		/* todo */
+	} else {
+		info->buf = raw_image;
+		info->count = image_size;
+	}
+
+	return 0;
+}
+
+/**
+ * region_parse_section_flat_dt - Allocate memory and copy the flat_dt image
+ *
+ * FPGA image header is in a contiguous buffer (region->info->header).
+ *
+ * @region: FPGA region
+ * @node_offset: offset to the flat_dt node
+ *
+ * Return: 0 for success or negative error code.
+ */
+static int region_parse_section_flat_dt(struct fpga_region *region,
+					int node_offset)
+{
+	struct device *dev = &region->dev;
+	struct fpga_image_info *info = region->info;
+	size_t fdt_size;
+	char *fdt_blob;
+	const void *fdt_data;
+
+	fdt_size = get_data_prop(info, node_offset, &fdt_data);
+	if (!fdt_size)
+		return -EINVAL;
+
+	pr_err("fdt_size   = %d\n", fdt_size);
+
+	/* Save the DT overlay.  We will apply it after FPGA programming */
+	if (info->sgt) {
+		/* todo */
+	} else {
+		fdt_blob = devm_kmemdup(dev, fdt_data, fdt_size, GFP_KERNEL);
+		if (!fdt_blob)
+			return -ENOMEM;
+		info->fdt_blob = fdt_blob;
+	}
+
+	if (fdt_size != fdt_totalsize(info->fdt_blob)) {
+		dev_err(dev, "FDT overlay size mismatch %d vs %d\n",
+			fdt_size, fdt_totalsize(info->fdt_blob));
+		devm_kfree(dev, info->fdt_blob);
+		info->fdt_blob = NULL;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * fpga_region_parse_images - find images in the header and parse them
+ * Assume there is at maximum one fpga image and one flat_dt image.
+ *
+ * FPGA image header is in a contiguous buffer (region->info->header).
+ *
+ * @region: FPGA region
+ *
+ * Return: 0 for success or negative error code.
+ */
+static int fpga_region_parse_images(struct fpga_region *region)
+{
+	struct device *dev = &region->dev;
+	struct fpga_image_info *info = region->info;
+	const char *type_prop;
+	int images_offset;
+	int ndepth, count, noffset, ret;
+
+	if (fdt_magic(info->header) != FDT_MAGIC) {
+		dev_err(dev, "Invalid magic for FPGA image\n");
+		return -EINVAL;
+	}
+
+	if (fdt_check_header(info->header)) {
+		dev_err(dev, "Invalid device tree blob header\n");
+		return -EINVAL;
+	}
+
+	/* Find the /images node */
+	images_offset = fdt_path_offset(info->header, "/images");
+	if (images_offset < 0) {
+		dev_err(dev, "could not find /images in FPGA image header\n");
+		return -EINVAL;
+	}
+
+	/* Find the first fpga and flat_dt sections, parse them. */
+	ndepth = 0;
+	noffset = fdt_next_node(info->header, images_offset, &ndepth);
+	for (count = 0;	(noffset >= 0) && (ndepth > 0);
+	     noffset = fdt_next_node(info->header, noffset, &ndepth)) {
+		if (ndepth != 1)
+			continue;
+
+		type_prop = fdt_getprop(info->header, noffset, "type", NULL);
+
+		if (!strcmp(type_prop, "fpga")) {
+			ret = region_parse_section_fpga(region, noffset);
+		} else if (!strcmp(type_prop, "flat_dt")) {
+			ret = region_parse_section_flat_dt(region, noffset);
+		} else {
+			dev_err(dev,
+				"Unsupported FPGA image header node type (%s)\n",
+				type_prop);
+			return -EINVAL;
+		}
+
+		count++;
+	}
+
+	return 0;
+}
+
+/**
+ * fpga_region_parse_header - parse FPGA image header
+ *
+ * FPGA image is in region->info's sg list, contiguous buffer
+ * (info->header), or firmware.
+ *
+ * @region: FPGA region
+ *
+ * Parse the FPGA stream header, leave the results in the image info.
+ *
+ * Return: 0 for success or negative error code.
+ */
+static int fpga_region_parse_header(struct fpga_region *region)
+{
+	struct device *dev = &region->dev;
+	struct fpga_image_info *info = region->info;
+	int ret;
+
+	if (!info->header) {
+		if (info->sgt) {
+			/* todo */
+		} else if (info->fw) {
+			info->header = (char *)info->fw->data;
+		} else {
+			dev_err(dev, "No header\n");
+			return -EINVAL;
+		}
+	}
+
+	ret = fpga_region_parse_images(region);
+
+	return ret;
+}
+
 /**
  * fpga_region_program_fpga - program FPGA
  * @region: FPGA region
@@ -103,6 +422,9 @@  int fpga_region_program_fpga(struct fpga_region *region)
 	struct fpga_image_info *info = region->info;
 	int ret;
 
+	if (!region->info)
+		return -EINVAL;
+
 	region = fpga_region_get(region);
 	if (IS_ERR(region)) {
 		dev_err(dev, "failed to get FPGA region\n");
@@ -182,6 +504,120 @@  void fpga_region_free(struct fpga_region *region)
 }
 EXPORT_SYMBOL_GPL(fpga_region_free);
 
+/* Region's image info has sg/buf/firmware.  Parse it, apply it */
+int fpga_region_fdt_image_apply(struct fpga_region *region)
+{
+	struct device *dev = &region->dev;
+	struct fpga_image_info *info = region->info;
+	const struct firmware *fw;
+	int ret;
+
+	if (info->firmware_name) {
+		ret = request_firmware(&fw, info->firmware_name, dev);
+		if (ret)
+			return ret;
+		info->fw = fw;
+	}
+
+	ret = fpga_region_parse_header(region);
+	if (ret)
+		goto out_rel;
+
+	if (info->buf) {
+		ret = fpga_region_program_fpga(region);
+		if (ret)
+			goto out_rel;
+	}
+
+	if (info->fdt_blob) {
+		ret = of_fpga_region_overlay_apply(region);
+		if (ret) {
+			dev_err(dev, "failed to apply fdt in bitstream\n");
+			devm_kfree(dev, info->fdt_blob);
+			info->fdt_blob = NULL;
+		}
+	}
+
+out_rel:
+	if (info->fw) {
+		release_firmware(fw);
+		info->fw = NULL;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(fpga_region_fdt_image_apply);
+
+#if IS_ENABLED(CONFIG_FPGA_REGION_SYSFS)
+
+static ssize_t firmware_name_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct fpga_region *region = to_fpga_region(dev);
+	struct fpga_image_info *info = region->info;
+
+	if (info && info->firmware_name)
+		return sprintf(buf, "%s\n", info->firmware_name);
+
+	return 0;
+}
+
+static ssize_t firmware_name_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct fpga_region *region = to_fpga_region(dev);
+	struct fpga_image_info *info;
+	char *firmware_name;
+	int ret;
+
+	firmware_name = devm_kzalloc(dev, count, GFP_KERNEL);
+	if (!firmware_name)
+		return -ENOMEM;
+
+	ret = sscanf(buf, "%s", firmware_name);
+	if (ret != 1) {
+		devm_kfree(dev, firmware_name);
+		return -EINVAL;
+	}
+
+	/* If empty string input, free up the region */
+	if (!strnlen(firmware_name, count)) {
+		fpga_region_overlay_rm(region);
+		fpga_region_free(region);
+		devm_kfree(dev, firmware_name);
+		return count;
+	}
+
+	/* Must free up region if programmed */
+	if (region->info) {
+		dev_err(dev, "region already in use\n");
+		return -EINVAL;
+	}
+
+	info = fpga_image_info_alloc(dev);
+	if (!info)
+		return -ENOMEM;
+
+	info->firmware_name = firmware_name;
+	region->info = info;
+	ret = fpga_region_fdt_image_apply(region);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(firmware_name);
+
+static struct attribute *fpga_region_attrs[] = {
+	&dev_attr_firmware_name.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(fpga_region);
+
+#endif /* CONFIG_FPGA_REGION_SYSFS */
+
 int fpga_region_register(struct device *dev, struct fpga_region *region)
 {
 	int id, ret = 0;
@@ -242,6 +678,10 @@  static int __init fpga_region_init(void)
 
 	fpga_region_class->dev_release = fpga_region_dev_release;
 
+#if IS_ENABLED(CONFIG_FPGA_REGION_SYSFS)
+	fpga_region_class->dev_groups = fpga_region_groups;
+#endif /* CONFIG_FPGA_REGION_SYSFS */
+
 	return 0;
 }
 
diff --git a/drivers/fpga/of-fpga-region.c b/drivers/fpga/of-fpga-region.c
index 019ca37..034cd9d 100644
--- a/drivers/fpga/of-fpga-region.c
+++ b/drivers/fpga/of-fpga-region.c
@@ -375,6 +375,13 @@  static int of_fpga_region_notify(struct notifier_block *nb,
 	if (!region)
 		return NOTIFY_OK;
 
+	/*
+	 * If notification is because bitstream had a fdt in the header,
+	 * silently ignore notification.
+	 */
+	if (region->info && region->info->fdt_blob)
+		return NOTIFY_OK;
+
 	ret = 0;
 	switch (action) {
 	case OF_OVERLAY_PRE_APPLY:
diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h
index ddc56c0..fb91154 100644
--- a/include/linux/fpga/fpga-mgr.h
+++ b/include/linux/fpga/fpga-mgr.h
@@ -16,6 +16,7 @@ 
  * You should have received a copy of the GNU General Public License along with
  * this program.  If not, see <http://www.gnu.org/licenses/>.
  */
+#include <linux/firmware.h>
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
 
@@ -86,10 +87,14 @@  enum fpga_mgr_states {
  * @config_complete_timeout_us: maximum time for FPGA to switch to operating
  *	   status in the write_complete op.
  * @firmware_name: name of FPGA image firmware file
+ * @fw: FPGA image firmware
  * @sgt: scatter/gather table containing FPGA image
  * @buf: contiguous buffer containing FPGA image
  * @count: size of buf
+ * @fdt_blob: fdt from FPGA image header
+ * @header: header
  * @overlay: Device Tree overlay
+ * @ov_id: id of applied overlay
  */
 struct fpga_image_info {
 	struct device *dev;
@@ -98,11 +103,15 @@  struct fpga_image_info {
 	u32 disable_timeout_us;
 	u32 config_complete_timeout_us;
 	char *firmware_name;
+	const struct firmware *fw;
 	struct sg_table *sgt;
 	const char *buf;
 	size_t count;
+	char *fdt_blob;
+	char *header;
 #ifdef CONFIG_OF
 	struct device_node *overlay;
+	int ov_id;
 #endif
 };
 
diff --git a/include/linux/fpga/fpga-region.h b/include/linux/fpga/fpga-region.h
index 3b6f0b0..3839430 100644
--- a/include/linux/fpga/fpga-region.h
+++ b/include/linux/fpga/fpga-region.h
@@ -33,6 +33,7 @@  struct fpga_region *fpga_region_class_find(
 
 int fpga_region_program_fpga(struct fpga_region *region);
 void fpga_region_free(struct fpga_region *region);
+int fpga_region_fdt_image_apply(struct fpga_region *region);
 
 int fpga_region_register(struct device *dev, struct fpga_region *region);
 int fpga_region_unregister(struct fpga_region *region);