public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] Add dynamic CSU register sysfs interface
@ 2026-04-08 11:42 Ronak Jain
  2026-04-08 11:42 ` [PATCH 1/2] Documentation: ABI: add sysfs interface for ZynqMP CSU registers Ronak Jain
  2026-04-08 11:42 ` [PATCH 2/2] firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface Ronak Jain
  0 siblings, 2 replies; 4+ messages in thread
From: Ronak Jain @ 2026-04-08 11:42 UTC (permalink / raw)
  To: michal.simek, senthilnathan.thangaraj
  Cc: linux-kernel, linux-arm-kernel, ronak.jain

This patch series adds support for exposing CSU registers through a
sysfs interface. The implementation uses dynamic discovery via the
PM_QUERY_DATA firmware API to determine available registers at
runtime, making the interface flexible and maintainable without
requiring kernel changes when firmware capabilities evolve.

Background:

The ZynqMP platform has several CSU registers that are useful for
system configuration and debugging. Previously, accessing these
registers required direct memory access or custom tools. This series
provides a standardized sysfs interface that leverages existing
firmware APIs for secure access.

Key Features:

- Dynamic register discovery using PM_QUERY_DATA API
  * PM_QID_GET_NODE_COUNT: Query number of available registers
  * PM_QID_GET_NODE_NAME: Query register names by index
- Automatic sysfs attribute creation under csu_registers/ group
- Read operations via existing IOCTL_READ_REG firmware API
- Write operations via existing IOCTL_MASK_WRITE_REG firmware API
- Firmware-enforced access control for read-only registers

Currently Supported Registers:

- multiboot (CSU_MULTI_BOOT): Boot mode configuration
- idcode (CSU_IDCODE): Device identification (read-only)
- pcap-status (CSU_PCAP_STATUS): PCAP status (read-only)

The sysfs interface is available at:
  /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/

Usage Examples:

Reading a register:
  # cat /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/idcode

Writing a register (mask and value in hex):
  # echo "0xFFFFFFFF 0x0" > /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/multiboot


Testing:

- Verified register read operations return correct values
- Verified write operations update registers correctly
- Verified read-only registers reject write attempts
- Verified dynamic discovery works with different firmware versions


Ronak Jain (2):
  Documentation: ABI: add sysfs interface for ZynqMP CSU registers
  firmware: zynqmp: Add dynamic CSU register discovery and sysfs
    interface

 .../ABI/stable/sysfs-driver-firmware-zynqmp   |  33 +++
 MAINTAINERS                                   |  10 +
 drivers/firmware/xilinx/Makefile              |   2 +-
 drivers/firmware/xilinx/zynqmp-csu-reg.c      | 249 ++++++++++++++++++
 drivers/firmware/xilinx/zynqmp-csu-reg.h      |  18 ++
 drivers/firmware/xilinx/zynqmp.c              |   6 +
 include/linux/firmware/xlnx-zynqmp.h          |   4 +-
 7 files changed, 320 insertions(+), 2 deletions(-)
 create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.c
 create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/2] Documentation: ABI: add sysfs interface for ZynqMP CSU registers
  2026-04-08 11:42 [PATCH 0/2] Add dynamic CSU register sysfs interface Ronak Jain
@ 2026-04-08 11:42 ` Ronak Jain
  2026-04-08 11:42 ` [PATCH 2/2] firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface Ronak Jain
  1 sibling, 0 replies; 4+ messages in thread
From: Ronak Jain @ 2026-04-08 11:42 UTC (permalink / raw)
  To: michal.simek, senthilnathan.thangaraj
  Cc: linux-kernel, linux-arm-kernel, ronak.jain

Document the new sysfs interface that exposes Configuration Security
Unit (CSU) registers through the zynqmp-firmware driver.

The interface is available under:

  /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/

The CSU registers are discovered at boot time using the PM_QUERY_DATA
firmware API. The following registers are currently supported:

  - multiboot     (CSU_MULTI_BOOT)
  - idcode        (CSU_IDCODE, read-only)
  - pcap-status   (CSU_PCAP_STATUS, read-only)

Read operations use the existing IOCTL_READ_REG firmware interface,
while write operations use IOCTL_MASK_WRITE_REG.

Access control is enforced by the firmware. Write attempts to
read-only registers are rejected by firmware even though the sysfs file
permissions allow writes.

Document the ABI entry accordingly.

Signed-off-by: Ronak Jain <ronak.jain@amd.com>
---
 .../ABI/stable/sysfs-driver-firmware-zynqmp   | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
index c3fec3c835af..f537f7d9bb55 100644
--- a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
+++ b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
@@ -254,3 +254,36 @@ Description:
 		The expected result is 500.
 
 Users:		Xilinx
+
+What:		/sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/*
+Date:		March 2026
+KernelVersion:	7.1
+Contact:	"Ronak Jain" <ronak.jain@amd.com>
+Description:
+		Read/Write CSU (Configuration Security Unit) registers.
+
+		This interface provides dynamic access to CSU registers that are
+		discovered from the firmware at boot time using PM_QUERY_DATA API.
+
+		The supported registers are:
+
+		- multiboot: CSU_MULTI_BOOT register
+		- idcode: CSU_IDCODE register (read-only)
+		- pcap-status: CSU_PCAP_STATUS register (read-only)
+
+		Read operations use the existing IOCTL_READ_REG API.
+		Write operations use the existing IOCTL_MASK_WRITE_REG API.
+
+		The firmware enforces access control - read-only registers will reject
+		write attempts even though the sysfs permissions show write access.
+
+		Usage for reading::
+
+		    # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot
+		    # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/idcode
+
+		Usage for writing (mask and value are in hexadecimal)::
+
+		    # echo 0xFFFFFFF 0x0 > /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot
+
+Users:		Xilinx/AMD
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface
  2026-04-08 11:42 [PATCH 0/2] Add dynamic CSU register sysfs interface Ronak Jain
  2026-04-08 11:42 ` [PATCH 1/2] Documentation: ABI: add sysfs interface for ZynqMP CSU registers Ronak Jain
@ 2026-04-08 11:42 ` Ronak Jain
  2026-04-23 11:34   ` Michal Simek
  1 sibling, 1 reply; 4+ messages in thread
From: Ronak Jain @ 2026-04-08 11:42 UTC (permalink / raw)
  To: michal.simek, senthilnathan.thangaraj
  Cc: linux-kernel, linux-arm-kernel, ronak.jain

Add support for dynamically discovering and exposing Configuration
Security Unit (CSU) registers through sysfs. Leverage the existing
PM_QUERY_DATA API to discover available registers at runtime, making
the interface flexible and maintainable.

Key features:
- Dynamic register discovery using PM_QUERY_DATA API
  * PM_QID_GET_NODE_COUNT: Query number of available registers
  * PM_QID_GET_NODE_NAME: Query register names by index
- Automatic sysfs attribute creation under csu_registers/ group
- Read operations via existing IOCTL_READ_REG API
- Write operations via existing IOCTL_MASK_WRITE_REG API
- Firmware-enforced access control (read-only registers reject writes)

The sysfs interface is created at:
  /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/

Currently supported registers include:
  - multiboot (CSU_MULTI_BOOT)
  - idcode (CSU_IDCODE, read-only)
  - pcap-status (CSU_PCAP_STATUS, read-only)

The dynamic discovery approach allows firmware to control which
registers are exposed without requiring kernel changes, improving
maintainability and security.

Signed-off-by: Ronak Jain <ronak.jain@amd.com>
---
 MAINTAINERS                              |  10 +
 drivers/firmware/xilinx/Makefile         |   2 +-
 drivers/firmware/xilinx/zynqmp-csu-reg.c | 249 +++++++++++++++++++++++
 drivers/firmware/xilinx/zynqmp-csu-reg.h |  18 ++
 drivers/firmware/xilinx/zynqmp.c         |   6 +
 include/linux/firmware/xlnx-zynqmp.h     |   4 +-
 6 files changed, 287 insertions(+), 2 deletions(-)
 create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.c
 create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 10d12b51b1f6..37fe2b7e0ccf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29212,6 +29212,16 @@ F:	drivers/dma/xilinx/xdma.c
 F:	include/linux/dma/amd_xdma.h
 F:	include/linux/platform_data/amd_xdma.h
 
+XILINX ZYNQMP CSU REGISTER DRIVER
+M:	Senthil Nathan Thangaraj <senthilnathan.thangaraj@amd.com>
+R:	Michal Simek <michal.simek@amd.com>
+R:	Ronak Jain <ronak.jain@amd.com>
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+S:	Maintained
+F:	Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
+F:	drivers/firmware/xilinx/zynqmp-csu-reg.c
+F:	drivers/firmware/xilinx/zynqmp-csu-reg.h
+
 XILINX ZYNQMP DPDMA DRIVER
 M:	Laurent Pinchart <laurent.pinchart@ideasonboard.com>
 L:	dmaengine@vger.kernel.org
diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile
index 8db0e66b6b7e..6203f41daaa6 100644
--- a/drivers/firmware/xilinx/Makefile
+++ b/drivers/firmware/xilinx/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 # Makefile for Xilinx firmwares
 
-obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o
+obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o zynqmp-csu-reg.o
 obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o
diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.c b/drivers/firmware/xilinx/zynqmp-csu-reg.c
new file mode 100644
index 000000000000..1f304ce858b1
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp-csu-reg.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Zynq MPSoC CSU Register Access
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ *  Michal Simek <michal.simek@amd.com>
+ *  Ronak Jain <ronak.jain@amd.com>
+ */
+
+#include <linux/firmware/xlnx-zynqmp.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "zynqmp-csu-reg.h"
+
+/* Node ID for CSU module in firmware */
+#define CSU_NODE_ID 0
+
+/* Maximum number of CSU registers supported */
+#define MAX_CSU_REGS 50
+
+/* Size of register name returned by firmware (3 u32 words = 12 bytes) */
+#define CSU_REG_NAME_LEN 12
+
+/**
+ * struct zynqmp_csu_reg - CSU register information
+ * @id: Register index from firmware
+ * @name: Register name
+ * @attr: Device attribute for sysfs
+ */
+struct zynqmp_csu_reg {
+	u32 id;
+	char name[CSU_REG_NAME_LEN];
+	struct device_attribute attr;
+};
+
+/**
+ * struct zynqmp_csu_data - Per-device CSU data
+ * @csu_regs: Array of CSU registers
+ * @csu_reg_count: Number of CSU registers
+ * @csu_attr_group: Attribute group for sysfs
+ */
+struct zynqmp_csu_data {
+	struct zynqmp_csu_reg *csu_regs;
+	int csu_reg_count;
+	struct attribute_group csu_attr_group;
+};
+
+/**
+ * zynqmp_pm_get_node_count() - Get number of supported nodes via QUERY_DATA
+ *
+ * Return: Number of nodes on success, or negative error code
+ */
+static int zynqmp_pm_get_node_count(void)
+{
+	struct zynqmp_pm_query_data qdata = {0};
+	u32 ret_payload[PAYLOAD_ARG_CNT];
+	int ret;
+
+	qdata.qid = PM_QID_GET_NODE_COUNT;
+
+	ret = zynqmp_pm_query_data(qdata, ret_payload);
+	if (ret)
+		return ret;
+
+	return ret_payload[1];
+}
+
+/**
+ * zynqmp_pm_get_node_name() - Get node name via QUERY_DATA
+ * @index: Register index
+ * @name: Buffer to store register name
+ *
+ * Return: 0 on success, error code otherwise
+ */
+static int zynqmp_pm_get_node_name(u32 index, char *name)
+{
+	struct zynqmp_pm_query_data qdata = {0};
+	u32 ret_payload[PAYLOAD_ARG_CNT];
+	int ret;
+
+	qdata.qid = PM_QID_GET_NODE_NAME;
+	qdata.arg1 = index;
+
+	ret = zynqmp_pm_query_data(qdata, ret_payload);
+	if (ret)
+		return ret;
+
+	memcpy(name, &ret_payload[1], CSU_REG_NAME_LEN);
+	name[CSU_REG_NAME_LEN - 1] = '\0';
+
+	return 0;
+}
+
+/**
+ * zynqmp_csu_reg_show() - Generic show function for all registers
+ * @dev: Device pointer
+ * @attr: Device attribute
+ * @buf: Output buffer
+ *
+ * Return: Number of bytes written to buffer, or error code
+ */
+static ssize_t zynqmp_csu_reg_show(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct zynqmp_csu_reg *reg;
+	u32 value;
+	int ret;
+
+	/* Use container_of to get register directly */
+	reg = container_of(attr, struct zynqmp_csu_reg, attr);
+
+	ret = zynqmp_pm_sec_read_reg(CSU_NODE_ID, reg->id, &value);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "0x%08x\n", value);
+}
+
+/**
+ * zynqmp_csu_reg_store() - Generic store function for writable registers
+ * @dev: Device pointer
+ * @attr: Device attribute
+ * @buf: Input buffer
+ * @count: Buffer size
+ *
+ * Format: "mask value" - both mask and value required
+ * Example: echo "0xFFFFFFFF 0x12345678" > register
+ *
+ * Return: count on success, error code otherwise
+ */
+static ssize_t zynqmp_csu_reg_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct zynqmp_csu_reg *reg;
+	u32 mask, value;
+	int ret;
+
+	reg = container_of(attr, struct zynqmp_csu_reg, attr);
+
+	if (sscanf(buf, "%x %x", &mask, &value) != 2)
+		return -EINVAL;
+
+	ret = zynqmp_pm_sec_mask_write_reg(CSU_NODE_ID, reg->id, mask, value);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+/**
+ * zynqmp_csu_discover_registers() - Discover CSU registers from firmware
+ * @pdev: Platform device pointer
+ *
+ * This function uses PM_QUERY_DATA to discover all available CSU registers
+ * and creates sysfs group under /sys/devices/platform/firmware:zynqmp-firmware/
+ *
+ * Return: 0 on success, error code otherwise
+ */
+int zynqmp_csu_discover_registers(struct platform_device *pdev)
+{
+	struct zynqmp_csu_data *csu_data;
+	struct attribute **attrs;
+	int count, ret, i;
+
+	ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_COUNT);
+	if (ret) {
+		dev_dbg(&pdev->dev, "CSU register discovery not supported by current firmware\n");
+		return 0;
+	}
+
+	count = zynqmp_pm_get_node_count();
+	if (count < 0)
+		return count;
+	if (count == 0) {
+		dev_dbg(&pdev->dev, "No nodes available from firmware\n");
+		return 0;
+	}
+
+	/* Validate count to prevent excessive memory allocation */
+	if (count > MAX_CSU_REGS) {
+		dev_err(&pdev->dev, "Register count %d exceeds maximum %d\n",
+			count, MAX_CSU_REGS);
+		return -EINVAL;
+	}
+
+	dev_dbg(&pdev->dev, "Discovered %d nodes from firmware\n", count);
+
+	csu_data = devm_kzalloc(&pdev->dev, sizeof(*csu_data), GFP_KERNEL);
+	if (!csu_data)
+		return -ENOMEM;
+
+	csu_data->csu_reg_count = count;
+
+	csu_data->csu_regs = devm_kcalloc(&pdev->dev, count, sizeof(*csu_data->csu_regs),
+					  GFP_KERNEL);
+	if (!csu_data->csu_regs) {
+		devm_kfree(&pdev->dev, csu_data);
+		return -ENOMEM;
+	}
+
+	attrs = devm_kcalloc(&pdev->dev, count + 1, sizeof(*attrs), GFP_KERNEL);
+	if (!attrs) {
+		devm_kfree(&pdev->dev, csu_data->csu_regs);
+		devm_kfree(&pdev->dev, csu_data);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < count; i++) {
+		struct zynqmp_csu_reg *reg = &csu_data->csu_regs[i];
+		struct device_attribute *dev_attr = &reg->attr;
+
+		reg->id = i;
+
+		ret = zynqmp_pm_get_node_name(i, reg->name);
+		if (ret) {
+			dev_warn(&pdev->dev, "Failed to get name for register %d\n", i);
+			snprintf(reg->name, sizeof(reg->name), "csu_reg_%d", i);
+		}
+
+		/* Create sysfs attribute - firmware enforces actual access control */
+		sysfs_attr_init(&dev_attr->attr);
+		dev_attr->attr.name = reg->name;
+		dev_attr->attr.mode = 0644;
+		dev_attr->show = zynqmp_csu_reg_show;
+		dev_attr->store = zynqmp_csu_reg_store;
+
+		attrs[i] = &dev_attr->attr;
+
+		dev_dbg(&pdev->dev, "Register %d: id=%d name=%s\n", i, reg->id, reg->name);
+	}
+
+	csu_data->csu_attr_group.name = "csu_registers";
+	csu_data->csu_attr_group.attrs = attrs;
+
+	ret = devm_device_add_group(&pdev->dev, &csu_data->csu_attr_group);
+	if (ret) {
+		devm_kfree(&pdev->dev, attrs);
+		devm_kfree(&pdev->dev, csu_data->csu_regs);
+		devm_kfree(&pdev->dev, csu_data);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(zynqmp_csu_discover_registers);
diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.h b/drivers/firmware/xilinx/zynqmp-csu-reg.h
new file mode 100644
index 000000000000..b12415db3496
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp-csu-reg.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx Zynq MPSoC CSU Register Access
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ *  Michal Simek <michal.simek@amd.com>
+ *  Ronak Jain <ronak.jain@amd.com>
+ */
+
+#ifndef __ZYNQMP_CSU_REG_H__
+#define __ZYNQMP_CSU_REG_H__
+
+#include <linux/platform_device.h>
+
+int zynqmp_csu_discover_registers(struct platform_device *pdev);
+
+#endif /* __ZYNQMP_CSU_REG_H__ */
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
index fbe8510f4927..b549d07f7497 100644
--- a/drivers/firmware/xilinx/zynqmp.c
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -27,6 +27,7 @@
 
 #include <linux/firmware/xlnx-zynqmp.h>
 #include <linux/firmware/xlnx-event-manager.h>
+#include "zynqmp-csu-reg.h"
 #include "zynqmp-debug.h"
 
 /* Max HashMap Order for PM API feature check (1<<7 = 128) */
@@ -2120,6 +2121,11 @@ static int zynqmp_firmware_probe(struct platform_device *pdev)
 			dev_err_probe(&pdev->dev, PTR_ERR(em_dev), "EM register fail with error\n");
 	}
 
+	/* Discover CSU registers dynamically */
+	ret = zynqmp_csu_discover_registers(pdev);
+	if (ret)
+		dev_warn(&pdev->dev, "CSU register discovery failed: %d\n", ret);
+
 	return of_platform_populate(dev->of_node, NULL, NULL, dev);
 }
 
diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
index d70dcd462b44..a4b293eb96ce 100644
--- a/include/linux/firmware/xlnx-zynqmp.h
+++ b/include/linux/firmware/xlnx-zynqmp.h
@@ -3,7 +3,7 @@
  * Xilinx Zynq MPSoC Firmware layer
  *
  *  Copyright (C) 2014-2021 Xilinx
- *  Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc.
+ *  Copyright (C) 2022 - 2026 Advanced Micro Devices, Inc.
  *
  *  Michal Simek <michal.simek@amd.com>
  *  Davorin Mista <davorin.mista@aggios.com>
@@ -262,6 +262,8 @@ enum pm_query_id {
 	PM_QID_CLOCK_GET_NUM_CLOCKS = 12,
 	PM_QID_CLOCK_GET_MAX_DIVISOR = 13,
 	PM_QID_PINCTRL_GET_ATTRIBUTES = 15,
+	PM_QID_GET_NODE_NAME = 16,
+	PM_QID_GET_NODE_COUNT = 17,
 };
 
 enum rpu_oper_mode {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 2/2] firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface
  2026-04-08 11:42 ` [PATCH 2/2] firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface Ronak Jain
@ 2026-04-23 11:34   ` Michal Simek
  0 siblings, 0 replies; 4+ messages in thread
From: Michal Simek @ 2026-04-23 11:34 UTC (permalink / raw)
  To: Ronak Jain, senthilnathan.thangaraj; +Cc: linux-kernel, linux-arm-kernel



On 4/8/26 13:42, Ronak Jain wrote:
> Add support for dynamically discovering and exposing Configuration
> Security Unit (CSU) registers through sysfs. Leverage the existing
> PM_QUERY_DATA API to discover available registers at runtime, making
> the interface flexible and maintainable.
> 
> Key features:
> - Dynamic register discovery using PM_QUERY_DATA API
>    * PM_QID_GET_NODE_COUNT: Query number of available registers
>    * PM_QID_GET_NODE_NAME: Query register names by index
> - Automatic sysfs attribute creation under csu_registers/ group
> - Read operations via existing IOCTL_READ_REG API
> - Write operations via existing IOCTL_MASK_WRITE_REG API
> - Firmware-enforced access control (read-only registers reject writes)
> 
> The sysfs interface is created at:
>    /sys/devices/platform/firmware:zynqmp-firmware/csu_registers/
> 
> Currently supported registers include:
>    - multiboot (CSU_MULTI_BOOT)
>    - idcode (CSU_IDCODE, read-only)
>    - pcap-status (CSU_PCAP_STATUS, read-only)
> 
> The dynamic discovery approach allows firmware to control which
> registers are exposed without requiring kernel changes, improving
> maintainability and security.
> 
> Signed-off-by: Ronak Jain <ronak.jain@amd.com>
> ---
>   MAINTAINERS                              |  10 +
>   drivers/firmware/xilinx/Makefile         |   2 +-
>   drivers/firmware/xilinx/zynqmp-csu-reg.c | 249 +++++++++++++++++++++++
>   drivers/firmware/xilinx/zynqmp-csu-reg.h |  18 ++
>   drivers/firmware/xilinx/zynqmp.c         |   6 +
>   include/linux/firmware/xlnx-zynqmp.h     |   4 +-
>   6 files changed, 287 insertions(+), 2 deletions(-)
>   create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.c
>   create mode 100644 drivers/firmware/xilinx/zynqmp-csu-reg.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 10d12b51b1f6..37fe2b7e0ccf 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -29212,6 +29212,16 @@ F:	drivers/dma/xilinx/xdma.c
>   F:	include/linux/dma/amd_xdma.h
>   F:	include/linux/platform_data/amd_xdma.h
>   
> +XILINX ZYNQMP CSU REGISTER DRIVER
> +M:	Senthil Nathan Thangaraj <senthilnathan.thangaraj@amd.com>
> +R:	Michal Simek <michal.simek@amd.com>
> +R:	Ronak Jain <ronak.jain@amd.com>
> +L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> +S:	Maintained
> +F:	Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
> +F:	drivers/firmware/xilinx/zynqmp-csu-reg.c
> +F:	drivers/firmware/xilinx/zynqmp-csu-reg.h
> +
>   XILINX ZYNQMP DPDMA DRIVER
>   M:	Laurent Pinchart <laurent.pinchart@ideasonboard.com>
>   L:	dmaengine@vger.kernel.org
> diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile
> index 8db0e66b6b7e..6203f41daaa6 100644
> --- a/drivers/firmware/xilinx/Makefile
> +++ b/drivers/firmware/xilinx/Makefile
> @@ -1,5 +1,5 @@
>   # SPDX-License-Identifier: GPL-2.0
>   # Makefile for Xilinx firmwares
>   
> -obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o
> +obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o zynqmp-csu-reg.o
>   obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o
> diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.c b/drivers/firmware/xilinx/zynqmp-csu-reg.c
> new file mode 100644
> index 000000000000..1f304ce858b1
> --- /dev/null
> +++ b/drivers/firmware/xilinx/zynqmp-csu-reg.c
> @@ -0,0 +1,249 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Xilinx Zynq MPSoC CSU Register Access
> + *
> + * Copyright (C) 2026 Advanced Micro Devices, Inc.
> + *
> + *  Michal Simek <michal.simek@amd.com>
> + *  Ronak Jain <ronak.jain@amd.com>
> + */
> +
> +#include <linux/firmware/xlnx-zynqmp.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +
> +#include "zynqmp-csu-reg.h"
> +
> +/* Node ID for CSU module in firmware */
> +#define CSU_NODE_ID 0
> +
> +/* Maximum number of CSU registers supported */
> +#define MAX_CSU_REGS 50
> +
> +/* Size of register name returned by firmware (3 u32 words = 12 bytes) */
> +#define CSU_REG_NAME_LEN 12
> +
> +/**
> + * struct zynqmp_csu_reg - CSU register information
> + * @id: Register index from firmware
> + * @name: Register name
> + * @attr: Device attribute for sysfs
> + */
> +struct zynqmp_csu_reg {
> +	u32 id;
> +	char name[CSU_REG_NAME_LEN];
> +	struct device_attribute attr;
> +};
> +
> +/**
> + * struct zynqmp_csu_data - Per-device CSU data
> + * @csu_regs: Array of CSU registers
> + * @csu_reg_count: Number of CSU registers
> + * @csu_attr_group: Attribute group for sysfs
> + */
> +struct zynqmp_csu_data {
> +	struct zynqmp_csu_reg *csu_regs;
> +	int csu_reg_count;
> +	struct attribute_group csu_attr_group;
> +};
> +
> +/**
> + * zynqmp_pm_get_node_count() - Get number of supported nodes via QUERY_DATA
> + *
> + * Return: Number of nodes on success, or negative error code
> + */
> +static int zynqmp_pm_get_node_count(void)
> +{
> +	struct zynqmp_pm_query_data qdata = {0};
> +	u32 ret_payload[PAYLOAD_ARG_CNT];
> +	int ret;
> +
> +	qdata.qid = PM_QID_GET_NODE_COUNT;
> +
> +	ret = zynqmp_pm_query_data(qdata, ret_payload);
> +	if (ret)
> +		return ret;
> +
> +	return ret_payload[1];
> +}
> +
> +/**
> + * zynqmp_pm_get_node_name() - Get node name via QUERY_DATA
> + * @index: Register index
> + * @name: Buffer to store register name
> + *
> + * Return: 0 on success, error code otherwise
> + */
> +static int zynqmp_pm_get_node_name(u32 index, char *name)
> +{
> +	struct zynqmp_pm_query_data qdata = {0};
> +	u32 ret_payload[PAYLOAD_ARG_CNT];
> +	int ret;
> +
> +	qdata.qid = PM_QID_GET_NODE_NAME;
> +	qdata.arg1 = index;
> +
> +	ret = zynqmp_pm_query_data(qdata, ret_payload);
> +	if (ret)
> +		return ret;
> +
> +	memcpy(name, &ret_payload[1], CSU_REG_NAME_LEN);
> +	name[CSU_REG_NAME_LEN - 1] = '\0';
> +
> +	return 0;
> +}
> +
> +/**
> + * zynqmp_csu_reg_show() - Generic show function for all registers
> + * @dev: Device pointer
> + * @attr: Device attribute
> + * @buf: Output buffer
> + *
> + * Return: Number of bytes written to buffer, or error code
> + */
> +static ssize_t zynqmp_csu_reg_show(struct device *dev,
> +				   struct device_attribute *attr,
> +				   char *buf)
> +{
> +	struct zynqmp_csu_reg *reg;
> +	u32 value;
> +	int ret;
> +
> +	/* Use container_of to get register directly */
> +	reg = container_of(attr, struct zynqmp_csu_reg, attr);
> +
> +	ret = zynqmp_pm_sec_read_reg(CSU_NODE_ID, reg->id, &value);
> +	if (ret)
> +		return ret;
> +
> +	return sysfs_emit(buf, "0x%08x\n", value);
> +}
> +
> +/**
> + * zynqmp_csu_reg_store() - Generic store function for writable registers
> + * @dev: Device pointer
> + * @attr: Device attribute
> + * @buf: Input buffer
> + * @count: Buffer size
> + *
> + * Format: "mask value" - both mask and value required
> + * Example: echo "0xFFFFFFFF 0x12345678" > register
> + *
> + * Return: count on success, error code otherwise
> + */
> +static ssize_t zynqmp_csu_reg_store(struct device *dev,
> +				    struct device_attribute *attr,
> +				    const char *buf, size_t count)
> +{
> +	struct zynqmp_csu_reg *reg;
> +	u32 mask, value;
> +	int ret;
> +
> +	reg = container_of(attr, struct zynqmp_csu_reg, attr);
> +
> +	if (sscanf(buf, "%x %x", &mask, &value) != 2)
> +		return -EINVAL;
> +
> +	ret = zynqmp_pm_sec_mask_write_reg(CSU_NODE_ID, reg->id, mask, value);
> +	if (ret)
> +		return ret;
> +
> +	return count;
> +}
> +
> +/**
> + * zynqmp_csu_discover_registers() - Discover CSU registers from firmware
> + * @pdev: Platform device pointer
> + *
> + * This function uses PM_QUERY_DATA to discover all available CSU registers
> + * and creates sysfs group under /sys/devices/platform/firmware:zynqmp-firmware/
> + *
> + * Return: 0 on success, error code otherwise
> + */
> +int zynqmp_csu_discover_registers(struct platform_device *pdev)
> +{
> +	struct zynqmp_csu_data *csu_data;
> +	struct attribute **attrs;
> +	int count, ret, i;
> +
> +	ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_COUNT);
> +	if (ret) {
> +		dev_dbg(&pdev->dev, "CSU register discovery not supported by current firmware\n");
> +		return 0;
> +	}
> +
> +	count = zynqmp_pm_get_node_count();
> +	if (count < 0)
> +		return count;
> +	if (count == 0) {
> +		dev_dbg(&pdev->dev, "No nodes available from firmware\n");
> +		return 0;
> +	}
> +
> +	/* Validate count to prevent excessive memory allocation */
> +	if (count > MAX_CSU_REGS) {
> +		dev_err(&pdev->dev, "Register count %d exceeds maximum %d\n",
> +			count, MAX_CSU_REGS);
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(&pdev->dev, "Discovered %d nodes from firmware\n", count);
> +
> +	csu_data = devm_kzalloc(&pdev->dev, sizeof(*csu_data), GFP_KERNEL);
> +	if (!csu_data)
> +		return -ENOMEM;
> +
> +	csu_data->csu_reg_count = count;

Where is this used?

> +
> +	csu_data->csu_regs = devm_kcalloc(&pdev->dev, count, sizeof(*csu_data->csu_regs),
> +					  GFP_KERNEL);
> +	if (!csu_data->csu_regs) {
> +		devm_kfree(&pdev->dev, csu_data);

My bet is that you are freeing this memory because csu is optional and you don't 
want to waste memory. Is this correct assumption?
If yes, I think it will be good if you can express this intention in commit 
message.


> +		return -ENOMEM;
> +	}
> +
> +	attrs = devm_kcalloc(&pdev->dev, count + 1, sizeof(*attrs), GFP_KERNEL);
> +	if (!attrs) {
> +		devm_kfree(&pdev->dev, csu_data->csu_regs);
> +		devm_kfree(&pdev->dev, csu_data);
> +		return -ENOMEM;
> +	}
> +
> +	for (i = 0; i < count; i++) {
> +		struct zynqmp_csu_reg *reg = &csu_data->csu_regs[i];
> +		struct device_attribute *dev_attr = &reg->attr;
> +
> +		reg->id = i;
> +
> +		ret = zynqmp_pm_get_node_name(i, reg->name);
> +		if (ret) {
> +			dev_warn(&pdev->dev, "Failed to get name for register %d\n", i);
> +			snprintf(reg->name, sizeof(reg->name), "csu_reg_%d", i);
> +		}
> +
> +		/* Create sysfs attribute - firmware enforces actual access control */
> +		sysfs_attr_init(&dev_attr->attr);
> +		dev_attr->attr.name = reg->name;
> +		dev_attr->attr.mode = 0644;

You should comment this a little bit. In commit message you are saying that some 
of that registers are read only but here you are exposing all of them as rw.

Do you have any interface/attribute/flag where you can find out if this is read 
only?

I expect if you try to write to read only firmware driver will just reject it 
anyway. If you don't have any flag for it, it would be good to describe this 
limiation in commit message.


> +		dev_attr->show = zynqmp_csu_reg_show;
> +		dev_attr->store = zynqmp_csu_reg_store;
> +
> +		attrs[i] = &dev_attr->attr;
> +
> +		dev_dbg(&pdev->dev, "Register %d: id=%d name=%s\n", i, reg->id, reg->name);
> +	}
> +
> +	csu_data->csu_attr_group.name = "csu_registers";
> +	csu_data->csu_attr_group.attrs = attrs;
> +
> +	ret = devm_device_add_group(&pdev->dev, &csu_data->csu_attr_group);
> +	if (ret) {
> +		devm_kfree(&pdev->dev, attrs);
> +		devm_kfree(&pdev->dev, csu_data->csu_regs);
> +		devm_kfree(&pdev->dev, csu_data);
> +	}
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(zynqmp_csu_discover_registers);
> diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.h b/drivers/firmware/xilinx/zynqmp-csu-reg.h
> new file mode 100644
> index 000000000000..b12415db3496
> --- /dev/null
> +++ b/drivers/firmware/xilinx/zynqmp-csu-reg.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Xilinx Zynq MPSoC CSU Register Access
> + *
> + * Copyright (C) 2026 Advanced Micro Devices, Inc.
> + *
> + *  Michal Simek <michal.simek@amd.com>
> + *  Ronak Jain <ronak.jain@amd.com>
> + */
> +
> +#ifndef __ZYNQMP_CSU_REG_H__
> +#define __ZYNQMP_CSU_REG_H__
> +
> +#include <linux/platform_device.h>
> +
> +int zynqmp_csu_discover_registers(struct platform_device *pdev);
> +
> +#endif /* __ZYNQMP_CSU_REG_H__ */
> diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
> index fbe8510f4927..b549d07f7497 100644
> --- a/drivers/firmware/xilinx/zynqmp.c
> +++ b/drivers/firmware/xilinx/zynqmp.c
> @@ -27,6 +27,7 @@
>   
>   #include <linux/firmware/xlnx-zynqmp.h>
>   #include <linux/firmware/xlnx-event-manager.h>
> +#include "zynqmp-csu-reg.h"
>   #include "zynqmp-debug.h"
>   
>   /* Max HashMap Order for PM API feature check (1<<7 = 128) */
> @@ -2120,6 +2121,11 @@ static int zynqmp_firmware_probe(struct platform_device *pdev)
>   			dev_err_probe(&pdev->dev, PTR_ERR(em_dev), "EM register fail with error\n");
>   	}
>   
> +	/* Discover CSU registers dynamically */
> +	ret = zynqmp_csu_discover_registers(pdev);
> +	if (ret)
> +		dev_warn(&pdev->dev, "CSU register discovery failed: %d\n", ret);
> +
>   	return of_platform_populate(dev->of_node, NULL, NULL, dev);
>   }
>   
> diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
> index d70dcd462b44..a4b293eb96ce 100644
> --- a/include/linux/firmware/xlnx-zynqmp.h
> +++ b/include/linux/firmware/xlnx-zynqmp.h
> @@ -3,7 +3,7 @@
>    * Xilinx Zynq MPSoC Firmware layer
>    *
>    *  Copyright (C) 2014-2021 Xilinx
> - *  Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc.
> + *  Copyright (C) 2022 - 2026 Advanced Micro Devices, Inc.
>    *
>    *  Michal Simek <michal.simek@amd.com>
>    *  Davorin Mista <davorin.mista@aggios.com>
> @@ -262,6 +262,8 @@ enum pm_query_id {
>   	PM_QID_CLOCK_GET_NUM_CLOCKS = 12,
>   	PM_QID_CLOCK_GET_MAX_DIVISOR = 13,
>   	PM_QID_PINCTRL_GET_ATTRIBUTES = 15,
> +	PM_QID_GET_NODE_NAME = 16,
> +	PM_QID_GET_NODE_COUNT = 17,
>   };
>   
>   enum rpu_oper_mode {

Thanks,
Michal

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-04-23 11:34 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-08 11:42 [PATCH 0/2] Add dynamic CSU register sysfs interface Ronak Jain
2026-04-08 11:42 ` [PATCH 1/2] Documentation: ABI: add sysfs interface for ZynqMP CSU registers Ronak Jain
2026-04-08 11:42 ` [PATCH 2/2] firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface Ronak Jain
2026-04-23 11:34   ` Michal Simek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox