mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 01:08:13 -04:00
Merge tag 'zynqmp-soc-for-7.2' of https://github.com/Xilinx/linux-xlnx into soc/drivers
arm64: Xilinx SOC changes for 7.2 firmware: - Add CSU register discovery with sysfs interface zynqmp_power: - Fix race condition in event registration - Fix shutdown and free rx mailbox channel * tag 'zynqmp-soc-for-7.2' of https://github.com/Xilinx/linux-xlnx: firmware: zynqmp: Add dynamic CSU register discovery and sysfs interface Documentation: ABI: add sysfs interface for ZynqMP CSU registers soc: xilinx: Shutdown and free rx mailbox channel soc: xilinx: Fix race condition in event registration Signed-off-by: Linus Walleij <linusw@kernel.org>
This commit is contained in:
@@ -254,3 +254,36 @@ Description:
|
||||
The expected result is 500.
|
||||
|
||||
Users: Xilinx
|
||||
|
||||
What: /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/*
|
||||
Date: May 2026
|
||||
KernelVersion: 7.2
|
||||
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
|
||||
|
||||
10
MAINTAINERS
10
MAINTAINERS
@@ -29272,6 +29272,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
|
||||
|
||||
@@ -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
|
||||
|
||||
258
drivers/firmware/xilinx/zynqmp-csu-reg.c
Normal file
258
drivers/firmware/xilinx/zynqmp-csu-reg.c
Normal file
@@ -0,0 +1,258 @@
|
||||
// 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_attr_group: Attribute group for sysfs
|
||||
*/
|
||||
struct zynqmp_csu_data {
|
||||
struct zynqmp_csu_reg *csu_regs;
|
||||
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;
|
||||
}
|
||||
|
||||
ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_NAME);
|
||||
if (ret) {
|
||||
dev_dbg(&pdev->dev, "CSU register name query 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_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 = ®->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);
|
||||
}
|
||||
|
||||
/*
|
||||
* The firmware does not expose per-register access mode via
|
||||
* PM_QUERY_DATA today, so the kernel cannot tell read-only
|
||||
* registers from read-write ones at discovery time. Expose
|
||||
* every register as 0644 and rely on the firmware to reject
|
||||
* IOCTL_MASK_WRITE_REG on read-only registers; the error is
|
||||
* propagated back to userspace from the store callback.
|
||||
*/
|
||||
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);
|
||||
18
drivers/firmware/xilinx/zynqmp-csu-reg.h
Normal file
18
drivers/firmware/xilinx/zynqmp-csu-reg.h
Normal file
@@ -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__ */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -303,18 +303,18 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
|
||||
* is not available to use) or -ENODEV(Xilinx Event Manager not compiled),
|
||||
* then use ipi-mailbox or interrupt method.
|
||||
*/
|
||||
zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct zynqmp_pm_work_struct),
|
||||
GFP_KERNEL);
|
||||
if (!zynqmp_pm_init_suspend_work)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
|
||||
zynqmp_pm_init_suspend_work_fn);
|
||||
|
||||
ret = register_event(&pdev->dev, PM_INIT_SUSPEND_CB, 0, 0, false,
|
||||
suspend_event_callback);
|
||||
if (!ret) {
|
||||
zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct zynqmp_pm_work_struct),
|
||||
GFP_KERNEL);
|
||||
if (!zynqmp_pm_init_suspend_work)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
|
||||
zynqmp_pm_init_suspend_work_fn);
|
||||
|
||||
ret = zynqmp_pm_get_family_info(&pm_family_code);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
@@ -326,14 +326,6 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
|
||||
else
|
||||
return -ENODEV;
|
||||
|
||||
ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART,
|
||||
false, subsystem_restart_event_callback);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
zynqmp_pm_init_restart_work = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct zynqmp_pm_work_struct),
|
||||
GFP_KERNEL);
|
||||
@@ -342,19 +334,18 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
|
||||
|
||||
INIT_WORK(&zynqmp_pm_init_restart_work->callback_work,
|
||||
zynqmp_pm_subsystem_restart_work_fn);
|
||||
|
||||
ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART,
|
||||
false, subsystem_restart_event_callback);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
} else if (ret != -EACCES && ret != -ENODEV) {
|
||||
dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret);
|
||||
return ret;
|
||||
} else if (of_property_present(pdev->dev.of_node, "mboxes")) {
|
||||
zynqmp_pm_init_suspend_work =
|
||||
devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct zynqmp_pm_work_struct),
|
||||
GFP_KERNEL);
|
||||
if (!zynqmp_pm_init_suspend_work)
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
|
||||
zynqmp_pm_init_suspend_work_fn);
|
||||
client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
|
||||
if (!client)
|
||||
return -ENOMEM;
|
||||
@@ -398,8 +389,10 @@ static void zynqmp_pm_remove(struct platform_device *pdev)
|
||||
{
|
||||
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
|
||||
|
||||
if (!rx_chan)
|
||||
if (rx_chan) {
|
||||
mbox_free_channel(rx_chan);
|
||||
rx_chan = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct of_device_id pm_of_match[] = {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user