Linux Tegra architecture development
 help / color / mirror / Atom feed
* [PATCH v2 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support
@ 2026-07-22 11:05 Aniruddha Rao
  2026-07-22 11:05 ` [PATCH v2 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
                   ` (4 more replies)
  0 siblings, 5 replies; 9+ messages in thread
From: Aniruddha Rao @ 2026-07-22 11:05 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

This series adds ACPI support to the Tegra BPMP driver and support for
Memory Bandwidth Throttler (MBWT) controls exposed by BPMP firmware of
some Tegra platforms.

The current BPMP driver depends on device tree bindings. This series
keeps the existing DT path unchanged while adding an ACPI transport path
for BPMP firmware requests.

MBWT controls are accessed through BPMP firmware requests. This series
defines the MBWT BPMP ABI, adds MBWT helper functions, and exposes a
narrow sysfs interface when firmware reports support for the required
MBWT requests.

Aniruddha Rao (5):
  firmware: tegra: bpmp: Move channel initialization to helper
  firmware: tegra: bpmp: Add ACPI support
  firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI
    definitions
  firmware: tegra: bpmp: Add MBWT BPMP helpers
  firmware: tegra: bpmp: Add MBWT sysfs interface

---
Changes since v1:
- Dropped the SoC Kconfig patch.
- Kept the existing DT path unchanged while adding ACPI support.
- Reworked ACPI matching to avoid a dummy ops table.
- Added explicit error handling for short ACPI BMRQ responses.
- Folded MBWT helpers into bpmp.c.
- Reworked MBWT sysfs into per-traffic bandwidth attributes.
- Removed software bandwidth range checks.
- Use NVCLINK consistently in commit messages and documentation.
- Avoid platform-specific or transport-specific MBWT wording.

 .../ABI/testing/sysfs-platform-tegra-bpmp     |  48 +++
 drivers/firmware/tegra/Makefile               |   1 +
 drivers/firmware/tegra/bpmp-private.h         |  16 +
 drivers/firmware/tegra/bpmp-tegra-sysfs.c     | 205 ++++++++++
 drivers/firmware/tegra/bpmp.c                 | 357 +++++++++++++++---
 include/soc/tegra/bpmp-abi.h                  | 161 +++++++-
 6 files changed, 736 insertions(+), 52 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-tegra-bpmp
 create mode 100644 drivers/firmware/tegra/bpmp-tegra-sysfs.c

-- 
2.43.0

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

* [PATCH v2 1/5] firmware: tegra: bpmp: Move channel initialization to helper
  2026-07-22 11:05 [PATCH v2 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
@ 2026-07-22 11:05 ` Aniruddha Rao
  2026-07-22 11:05 ` [PATCH v2 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 9+ messages in thread
From: Aniruddha Rao @ 2026-07-22 11:05 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Move BPMP channel allocation and initialization into a helper.

This keeps the probe function focused on sequencing.

It also lets later patches decide whether mailbox/IVC setup is needed.

No functional change is intended.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v1:
- Keep OF resource initialization in tegra_bpmp_probe().
- Move only BPMP channel allocation and initialization into a helper.

 drivers/firmware/tegra/bpmp.c | 44 +++++++++++++++++++++--------------
 1 file changed, 27 insertions(+), 17 deletions(-)

diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 753472b53bd8..0eb99b1c5068 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -733,19 +733,9 @@ void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp)
 	spin_unlock(&bpmp->lock);
 }
 
-static int tegra_bpmp_probe(struct platform_device *pdev)
+static int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
 {
-	struct tegra_bpmp *bpmp;
-	char tag[TAG_SZ];
 	size_t size;
-	int err;
-
-	bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
-	if (!bpmp)
-		return -ENOMEM;
-
-	bpmp->soc = of_device_get_match_data(&pdev->dev);
-	bpmp->dev = &pdev->dev;
 
 	INIT_LIST_HEAD(&bpmp->mrqs);
 	spin_lock_init(&bpmp->lock);
@@ -755,31 +745,51 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 
 	size = BITS_TO_LONGS(bpmp->threaded.count) * sizeof(long);
 
-	bpmp->threaded.allocated = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	bpmp->threaded.allocated = devm_kzalloc(bpmp->dev, size, GFP_KERNEL);
 	if (!bpmp->threaded.allocated)
 		return -ENOMEM;
 
-	bpmp->threaded.busy = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	bpmp->threaded.busy = devm_kzalloc(bpmp->dev, size, GFP_KERNEL);
 	if (!bpmp->threaded.busy)
 		return -ENOMEM;
 
 	spin_lock_init(&bpmp->atomic_tx_lock);
-	bpmp->tx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->tx_channel),
+	bpmp->tx_channel = devm_kzalloc(bpmp->dev, sizeof(*bpmp->tx_channel),
 					GFP_KERNEL);
 	if (!bpmp->tx_channel)
 		return -ENOMEM;
 
-	bpmp->rx_channel = devm_kzalloc(&pdev->dev, sizeof(*bpmp->rx_channel),
-	                                GFP_KERNEL);
+	bpmp->rx_channel = devm_kzalloc(bpmp->dev, sizeof(*bpmp->rx_channel),
+					GFP_KERNEL);
 	if (!bpmp->rx_channel)
 		return -ENOMEM;
 
-	bpmp->threaded_channels = devm_kcalloc(&pdev->dev, bpmp->threaded.count,
+	bpmp->threaded_channels = devm_kcalloc(bpmp->dev, bpmp->threaded.count,
 					       sizeof(*bpmp->threaded_channels),
 					       GFP_KERNEL);
 	if (!bpmp->threaded_channels)
 		return -ENOMEM;
 
+	return 0;
+}
+
+static int tegra_bpmp_probe(struct platform_device *pdev)
+{
+	struct tegra_bpmp *bpmp;
+	char tag[TAG_SZ];
+	int err;
+
+	bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
+	if (!bpmp)
+		return -ENOMEM;
+
+	bpmp->soc = of_device_get_match_data(&pdev->dev);
+	bpmp->dev = &pdev->dev;
+
+	err = tegra_bpmp_init_channels(bpmp);
+	if (err < 0)
+		return err;
+
 	platform_set_drvdata(pdev, bpmp);
 
 	err = bpmp->soc->ops->init(bpmp);
-- 
2.43.0

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

* [PATCH v2 2/5] firmware: tegra: bpmp: Add ACPI support
  2026-07-22 11:05 [PATCH v2 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
  2026-07-22 11:05 ` [PATCH v2 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
@ 2026-07-22 11:05 ` Aniruddha Rao
  2026-07-23  5:10   ` Mikko Perttunen
  2026-07-22 11:05 ` [PATCH v2 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 9+ messages in thread
From: Aniruddha Rao @ 2026-07-22 11:05 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Add required changes in the Tegra BPMP driver to make it compatible with
ACPI based platforms.

On ACPI systems, IPC is handled through the AML method instead of
the core kernel framework using mailboxes and IVC.

Bypass clock, reset and powergate init calls as these are not
controlled by the Linux drivers on ACPI based systems.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v1:
- Use acpi_extract_package() and a common cleanup path for BMRQ replies.
- Treat short BMRQ responses as errors.
- Rename the mailbox transport helper to __tegra_bpmp_transfer().
- Avoid a dummy ACPI ops table and call SoC ops only when present.
- Keep OF resource setup guarded by the OF node in probe.

 drivers/firmware/tegra/bpmp.c | 221 ++++++++++++++++++++++++++++------
 1 file changed, 182 insertions(+), 39 deletions(-)

diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 0eb99b1c5068..2dcb74a45b59 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -3,6 +3,7 @@
  * Copyright (c) 2016, NVIDIA CORPORATION.  All rights reserved.
  */
 
+#include <linux/acpi.h>
 #include <linux/clk/tegra.h>
 #include <linux/genalloc.h>
 #include <linux/mailbox_client.h>
@@ -11,8 +12,10 @@
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/pm.h>
+#include <linux/property.h>
 #include <linux/semaphore.h>
 #include <linux/sched/clock.h>
+#include <linux/slab.h>
 
 #include <soc/tegra/bpmp.h>
 #include <soc/tegra/bpmp-abi.h>
@@ -23,6 +26,12 @@
 #define MSG_ACK		BIT(0)
 #define MSG_RING	BIT(1)
 #define TAG_SZ		32
+#define TEGRA_BPMP_ACPI_BMRQ_DATA_SZ	3960U
+
+struct tegra_bpmp_acpi_message {
+	u64 status;
+	u8 *data;
+};
 
 static inline const struct tegra_bpmp_ops *
 channel_to_ops(struct tegra_bpmp_channel *channel)
@@ -343,12 +352,99 @@ static ssize_t tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel,
 
 static int __maybe_unused tegra_bpmp_resume(struct device *dev);
 
+#ifdef CONFIG_ACPI
+static int tegra_bpmp_transfer_acpi(struct tegra_bpmp *bpmp,
+				    struct tegra_bpmp_message *msg)
+{
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct acpi_buffer format = { sizeof("NB"), "NB" };
+	struct acpi_object_list param_list;
+	union acpi_object params[2];
+	struct tegra_bpmp_acpi_message *reply;
+	acpi_status status;
+	size_t data_len;
+	int err = 0;
+
+	if (!tegra_bpmp_message_valid(msg))
+		return -EINVAL;
+
+	params[0].type = ACPI_TYPE_INTEGER;
+	params[0].integer.value = msg->mrq;
+
+	params[1].type = ACPI_TYPE_BUFFER;
+	params[1].buffer.length = msg->tx.size;
+	params[1].buffer.pointer = (u8 *)msg->tx.data;
+
+	param_list.count = 2;
+	param_list.pointer = params;
+
+	status = acpi_evaluate_object(ACPI_HANDLE(bpmp->dev), "BMRQ",
+				      &param_list, &output);
+	if (ACPI_FAILURE(status)) {
+		acpi_evaluation_failure_warn(ACPI_HANDLE(bpmp->dev), "BMRQ",
+					     status);
+		return -ENODEV;
+	}
+
+	status = acpi_extract_package(output.pointer, &format, &response);
+	if (ACPI_FAILURE(status)) {
+		dev_err(bpmp->dev, "BMRQ: invalid response package: %s\n",
+			acpi_format_exception(status));
+		err = -ENODATA;
+		goto out;
+	}
+
+	if (response.length < sizeof(*reply)) {
+		dev_err(bpmp->dev, "BMRQ: response too short\n");
+		err = -ENODATA;
+		goto out;
+	}
+
+	reply = response.pointer;
+	data_len = response.length - sizeof(*reply);
+	if (data_len > TEGRA_BPMP_ACPI_BMRQ_DATA_SZ) {
+		dev_err(bpmp->dev, "BMRQ: reply buffer too large (%zu)\n",
+			data_len);
+		err = -EINVAL;
+		goto out;
+	}
+
+	msg->rx.ret = (int)reply->status;
+
+	if (msg->rx.data && msg->rx.size) {
+		if (data_len < msg->rx.size) {
+			dev_err(bpmp->dev, "BMRQ: response data too short\n");
+			err = -ENODATA;
+			goto out;
+		}
+
+		memcpy(msg->rx.data, reply->data, msg->rx.size);
+	}
+
+out:
+	kfree(response.pointer);
+	kfree(output.pointer);
+
+	return err;
+}
+#else
+static int tegra_bpmp_transfer_acpi(struct tegra_bpmp *bpmp,
+				    struct tegra_bpmp_message *msg)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+
 int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
 			       struct tegra_bpmp_message *msg)
 {
 	struct tegra_bpmp_channel *channel;
 	int err;
 
+	if (WARN_ON(ACPI_HANDLE(bpmp->dev)))
+		return -EOPNOTSUPP;
+
 	if (WARN_ON(!irqs_disabled()))
 		return -EPERM;
 
@@ -389,16 +485,13 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
 }
 EXPORT_SYMBOL_GPL(tegra_bpmp_transfer_atomic);
 
-int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
-			struct tegra_bpmp_message *msg)
+static int __tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
+				 struct tegra_bpmp_message *msg)
 {
 	struct tegra_bpmp_channel *channel;
 	unsigned long timeout;
 	int err;
 
-	if (WARN_ON(irqs_disabled()))
-		return -EPERM;
-
 	if (!tegra_bpmp_message_valid(msg))
 		return -EINVAL;
 
@@ -428,6 +521,18 @@ int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
 	return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size,
 				       &msg->rx.ret);
 }
+
+int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
+			struct tegra_bpmp_message *msg)
+{
+	if (WARN_ON(irqs_disabled()))
+		return -EPERM;
+
+	if (ACPI_HANDLE(bpmp->dev))
+		return tegra_bpmp_transfer_acpi(bpmp, msg);
+
+	return __tegra_bpmp_transfer(bpmp, msg);
+}
 EXPORT_SYMBOL_GPL(tegra_bpmp_transfer);
 
 static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp,
@@ -606,11 +711,17 @@ static int tegra_bpmp_ping(struct tegra_bpmp *bpmp)
 	msg.rx.data = &response;
 	msg.rx.size = sizeof(response);
 
-	local_irq_save(flags);
 	start = ktime_get();
-	err = tegra_bpmp_transfer_atomic(bpmp, &msg);
+
+	if (ACPI_HANDLE(bpmp->dev)) {
+		err = tegra_bpmp_transfer(bpmp, &msg);
+	} else {
+		local_irq_save(flags);
+		err = tegra_bpmp_transfer_atomic(bpmp, &msg);
+		local_irq_restore(flags);
+	}
+
 	end = ktime_get();
-	local_irq_restore(flags);
 
 	if (!err)
 		dev_dbg(bpmp->dev,
@@ -632,6 +743,9 @@ static int tegra_bpmp_get_firmware_tag_old(struct tegra_bpmp *bpmp, char *tag,
 	void *virt;
 	int err;
 
+	if (ACPI_HANDLE(bpmp->dev))
+		return -EOPNOTSUPP;
+
 	if (size != TAG_SZ)
 		return -EINVAL;
 
@@ -783,23 +897,32 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 	if (!bpmp)
 		return -ENOMEM;
 
-	bpmp->soc = of_device_get_match_data(&pdev->dev);
-	bpmp->dev = &pdev->dev;
+	bpmp->soc = device_get_match_data(&pdev->dev);
+	if (!bpmp->soc)
+		return -EINVAL;
 
-	err = tegra_bpmp_init_channels(bpmp);
-	if (err < 0)
-		return err;
+	bpmp->dev = &pdev->dev;
 
 	platform_set_drvdata(pdev, bpmp);
 
-	err = bpmp->soc->ops->init(bpmp);
-	if (err < 0)
-		return err;
+	if (!ACPI_HANDLE(bpmp->dev)) {
+		err = tegra_bpmp_init_channels(bpmp);
+		if (err < 0)
+			return err;
+	}
 
-	err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
-				     tegra_bpmp_mrq_handle_ping, bpmp);
-	if (err < 0)
-		goto deinit;
+	if (bpmp->soc->ops && bpmp->soc->ops->init) {
+		err = bpmp->soc->ops->init(bpmp);
+		if (err < 0)
+			return err;
+	}
+
+	if (!ACPI_HANDLE(bpmp->dev)) {
+		err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
+					     tegra_bpmp_mrq_handle_ping, bpmp);
+		if (err < 0)
+			goto deinit;
+	}
 
 	err = tegra_bpmp_ping(bpmp);
 	if (err < 0) {
@@ -815,26 +938,30 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 
 	dev_info(&pdev->dev, "firmware: %.*s\n", (int)sizeof(tag), tag);
 
-	err = of_platform_default_populate(pdev->dev.of_node, NULL, &pdev->dev);
-	if (err < 0)
-		goto free_mrq;
-
-	if (of_property_present(pdev->dev.of_node, "#clock-cells")) {
-		err = tegra_bpmp_init_clocks(bpmp);
+	if (pdev->dev.of_node) {
+		err = of_platform_default_populate(pdev->dev.of_node, NULL,
+						   &pdev->dev);
 		if (err < 0)
 			goto free_mrq;
-	}
 
-	if (of_property_present(pdev->dev.of_node, "#reset-cells")) {
-		err = tegra_bpmp_init_resets(bpmp);
-		if (err < 0)
-			goto free_mrq;
-	}
+		if (of_property_present(pdev->dev.of_node, "#clock-cells")) {
+			err = tegra_bpmp_init_clocks(bpmp);
+			if (err < 0)
+				goto free_mrq;
+		}
 
-	if (of_property_present(pdev->dev.of_node, "#power-domain-cells")) {
-		err = tegra_bpmp_init_powergates(bpmp);
-		if (err < 0)
-			goto free_mrq;
+		if (of_property_present(pdev->dev.of_node, "#reset-cells")) {
+			err = tegra_bpmp_init_resets(bpmp);
+			if (err < 0)
+				goto free_mrq;
+		}
+
+		if (of_property_present(pdev->dev.of_node,
+					"#power-domain-cells")) {
+			err = tegra_bpmp_init_powergates(bpmp);
+			if (err < 0)
+				goto free_mrq;
+		}
 	}
 
 	err = tegra_bpmp_init_debugfs(bpmp);
@@ -844,9 +971,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 	return 0;
 
 free_mrq:
-	tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
+	if (!ACPI_HANDLE(bpmp->dev))
+		tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
 deinit:
-	if (bpmp->soc->ops->deinit)
+	if (bpmp->soc->ops && bpmp->soc->ops->deinit)
 		bpmp->soc->ops->deinit(bpmp);
 
 	return err;
@@ -867,7 +995,7 @@ static int __maybe_unused tegra_bpmp_resume(struct device *dev)
 
 	bpmp->suspended = false;
 
-	if (bpmp->soc->ops->resume)
+	if (bpmp->soc->ops && bpmp->soc->ops->resume)
 		return bpmp->soc->ops->resume(bpmp);
 	else
 		return 0;
@@ -939,10 +1067,25 @@ static const struct of_device_id tegra_bpmp_match[] = {
 	{ }
 };
 
+#ifdef CONFIG_ACPI
+static const struct tegra_bpmp_soc tegra_bpmp_acpi_soc = { };
+
+static const struct acpi_device_id tegra_bpmp_acpi_match[] = {
+	{
+		.id = "NVDA3001",
+		.driver_data = (kernel_ulong_t)&tegra_bpmp_acpi_soc,
+	},
+	{ }
+};
+#endif
+
 static struct platform_driver tegra_bpmp_driver = {
 	.driver = {
 		.name = "tegra-bpmp",
 		.of_match_table = tegra_bpmp_match,
+#ifdef CONFIG_ACPI
+		.acpi_match_table = tegra_bpmp_acpi_match,
+#endif
 		.pm = &tegra_bpmp_pm_ops,
 		.suppress_bind_attrs = true,
 	},
-- 
2.43.0

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

* [PATCH v2 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions
  2026-07-22 11:05 [PATCH v2 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
  2026-07-22 11:05 ` [PATCH v2 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
  2026-07-22 11:05 ` [PATCH v2 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
@ 2026-07-22 11:05 ` Aniruddha Rao
  2026-07-22 11:05 ` [PATCH v2 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
  2026-07-22 11:05 ` [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Aniruddha Rao
  4 siblings, 0 replies; 9+ messages in thread
From: Aniruddha Rao @ 2026-07-22 11:05 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Different workloads can place different memory-bandwidth demands on the
system. BPMP firmware of some Tegra platforms provides Memory Bandwidth
Throttler (MBWT) controls for PCIe and GPU connected over chip-to-chip
link (NVCLINK) traffic on the path to DRAM so software can query and
program those bandwidth caps.

The MBWT controls are not exposed through ordinary host MMIO and are
accessible only through BPMP firmware requests.

Add the ABI definitions for the MBWT message request (MRQ), including
the MRQ ID, sub-command IDs, and request/response payloads needed to
issue the MBWT control requests defined by BPMP firmware.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v1:
- Use NVCLINK spelling consistently.

 include/soc/tegra/bpmp-abi.h | 161 ++++++++++++++++++++++++++++++++++-
 1 file changed, 160 insertions(+), 1 deletion(-)

diff --git a/include/soc/tegra/bpmp-abi.h b/include/soc/tegra/bpmp-abi.h
index 5c64b3e02211..6f25126f5fb8 100644
--- a/include/soc/tegra/bpmp-abi.h
+++ b/include/soc/tegra/bpmp-abi.h
@@ -587,12 +587,13 @@ struct mrq_response {
 #define MRQ_HWPM		93U
 #define MRQ_DVFS		94U
 #define MRQ_PPP_PROFILE		95U
+#define MRQ_SOCHUB_MBWT		96U
 
 /**
  * @brief Maximum MRQ code to be sent by CPU software to
  * BPMP. Subject to change in future
  */
-#define MAX_CPU_MRQ_ID		95U
+#define MAX_CPU_MRQ_ID		96U
 
 /** @} */
 
@@ -6686,6 +6687,164 @@ struct mrq_ppp_profile_response {
 /** @} PPP */
 /** @endcond */
 
+/** @cond (bpmp_tb500)
+ * @ingroup MRQ_Codes
+ * @def MRQ_SOCHUB_MBWT
+ * @brief Configure per-virtual-channel bandwidth caps for a bandwidth group using
+ *        Memory Bandwidth Throttler (MBWT).
+ *
+ * * Initiators: Any
+ * * Targets: BPMP
+ * * Request Payload: @ref mrq_sochub_mbwt_request
+ * * Response Payload: @ref mrq_sochub_mbwt_response
+ *
+ * @addtogroup SOCHUB_MBWT
+ * @{
+ */
+
+/**
+ * @brief Sub-command identifiers for #MRQ_SOCHUB_MBWT.
+ */
+enum mrq_sochub_mbwt_cmd {
+	/**
+	 * @brief Check whether the BPMP-FW supports the specified
+	 * #MRQ_SOCHUB_MBWT sub-command.
+	 *
+	 * mrq_response::err is 0 if the specified request is
+	 * supported and -#BPMP_ENODEV otherwise.
+	 */
+	CMD_SOCHUB_MBWT_QUERY_ABI = 0,
+	/**
+	 * @brief Get bandwidth cap for a virtual channel
+	 *
+	 * mrq_response::err is defined as:
+	 *
+	 * | Value          | Description                                    |
+	 * |----------------|------------------------------------------------|
+	 * | 0              | Success                                        |
+	 * | -#BPMP_ENOTSUP | #MRQ_SOCHUB_MBWT is not supported by BPMP-FW.  |
+	 * | -#BPMP_EINVAL  | Invalid request parameters.                    |
+	 * | -#BPMP_EIO     | Failed to retrieve the bandwidth.              |
+	 */
+	CMD_SOCHUB_MBWT_GET_BW = 1,
+	/**
+	 * @brief Set bandwidth cap for a virtual channel.
+	 *
+	 * mrq_response::err is defined as:
+	 *
+	 * | Value          | Description                                    |
+	 * |----------------|------------------------------------------------|
+	 * | 0              | Success                                        |
+	 * | -#BPMP_ENOTSUP | #MRQ_SOCHUB_MBWT is not supported by BPMP-FW.  |
+	 * | -#BPMP_EINVAL  | Invalid request parameters.                    |
+	 * | -#BPMP_EIO     | Failed to set the bandwidth.                   |
+	 */
+	CMD_SOCHUB_MBWT_SET_BW = 2,
+};
+
+/**
+ * @brief Request data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_QUERY_ABI
+ */
+struct cmd_sochub_mbwt_query_abi_req {
+	/** @brief Sub-command identifier from @ref mrq_sochub_mbwt_cmd */
+	uint32_t cmd_code;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Request data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_GET_BW
+ */
+struct cmd_sochub_mbwt_get_bw_req {
+	/**
+	 * @brief Instance ID for the bandwidth group
+	 *
+	 * Valid range is [0, 5]
+	 */
+	uint32_t instance;
+	/**
+	 * @brief Type of the virtual channel/traffic
+	 *
+	 * Valid values:
+	 *
+	 * * Value 0: PCIe read
+	 * * Value 1: PCIe write
+	 * * Value 2: GPU connected over the chip-to-chip link (NVCLINK)
+	 */
+	uint32_t vc_type;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Request data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_SET_BW
+ */
+struct cmd_sochub_mbwt_set_bw_req {
+	/**
+	 * @brief Instance ID for the bandwidth group
+	 *
+	 * Valid range is [0, 5]
+	 */
+	uint32_t instance;
+	/**
+	 * @brief Type of the virtual channel/traffic
+	 *
+	 * Valid values:
+	 *
+	 * * Value 0: PCIe read
+	 * * Value 1: PCIe write
+	 * * Value 2: GPU connected over the chip-to-chip link (NVCLINK)
+	 */
+	uint32_t vc_type;
+	/** @brief Bandwidth cap in GB/s */
+	uint32_t bw;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Response data for #MRQ_SOCHUB_MBWT sub-command
+ *        #CMD_SOCHUB_MBWT_GET_BW
+ */
+struct cmd_sochub_mbwt_get_bw_resp {
+	/** @brief Bandwidth cap in GB/s */
+	uint32_t bw;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Request payload for the #MRQ_SOCHUB_MBWT -command
+ *
+ * | Sub-command                   | Request payload                        |
+ * |-------------------------------|----------------------------------------|
+ * | #CMD_SOCHUB_MBWT_QUERY_ABI    | #cmd_sochub_mbwt_query_abi_req         |
+ * | #CMD_SOCHUB_MBWT_GET_BW       | #cmd_sochub_mbwt_get_bw_req            |
+ * | #CMD_SOCHUB_MBWT_SET_BW       | #cmd_sochub_mbwt_set_bw_req            |
+ */
+struct mrq_sochub_mbwt_request {
+	/** @brief Sub-command ID from @ref mrq_sochub_mbwt_cmd. */
+	uint32_t cmd;
+	union {
+		struct cmd_sochub_mbwt_query_abi_req query_abi;
+		struct cmd_sochub_mbwt_get_bw_req get_bw;
+		struct cmd_sochub_mbwt_set_bw_req set_bw;
+	} BPMP_UNION_ANON;
+} BPMP_ABI_PACKED;
+
+/**
+ * @brief Response payload for the #MRQ_SOCHUB_MBWT -command.
+ *
+ * | Sub-command                   | Response payload                       |
+ * |-------------------------------|----------------------------------------|
+ * | #CMD_SOCHUB_MBWT_QUERY_ABI    | -                                      |
+ * | #CMD_SOCHUB_MBWT_GET_BW       | #cmd_sochub_mbwt_get_bw_resp           |
+ * | #CMD_SOCHUB_MBWT_SET_BW       | -                                      |
+ */
+struct mrq_sochub_mbwt_response {
+	union {
+		struct cmd_sochub_mbwt_get_bw_resp get_bw;
+	} BPMP_UNION_ANON;
+} BPMP_ABI_PACKED;
+
+/** @} SOCHUB_MBWT */
+/** @endcond */
+
 /**
  * @addtogroup Error_Codes
  * Negative values for mrq_response::err generally indicate some
-- 
2.43.0

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

* [PATCH v2 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers
  2026-07-22 11:05 [PATCH v2 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
                   ` (2 preceding siblings ...)
  2026-07-22 11:05 ` [PATCH v2 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
@ 2026-07-22 11:05 ` Aniruddha Rao
  2026-07-23  5:15   ` Mikko Perttunen
  2026-07-22 11:05 ` [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Aniruddha Rao
  4 siblings, 1 reply; 9+ messages in thread
From: Aniruddha Rao @ 2026-07-22 11:05 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Add helper functions that send MBWT requests to BPMP firmware.

The helpers implement the GET_BW and SET_BW operations.

They also provide a QUERY_ABI-based probe for command availability.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v1:
- Use generic tegra_bpmp_mbwt_*() helper names.
- Fold the helpers into bpmp.c instead of adding a separate source file.
- Drop SoC-specific Kconfig dependencies from the helper patch.
- Return BPMP firmware errors directly.

 drivers/firmware/tegra/bpmp-private.h |  7 ++
 drivers/firmware/tegra/bpmp.c         | 98 +++++++++++++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index 07c3d46abb87..b3770e71020e 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -26,4 +26,11 @@ struct tegra_bpmp_ops {
 extern const struct tegra_bpmp_ops tegra186_bpmp_ops;
 extern const struct tegra_bpmp_ops tegra210_bpmp_ops;
 
+bool tegra_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
+				      unsigned int cmd_code);
+int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int *bandwidth);
+int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int bandwidth);
+
 #endif
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 2dcb74a45b59..2cf8490b8b2e 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -675,6 +675,104 @@ bool tegra_bpmp_mrq_is_supported(struct tegra_bpmp *bpmp, unsigned int mrq)
 }
 EXPORT_SYMBOL_GPL(tegra_bpmp_mrq_is_supported);
 
+bool tegra_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
+				      unsigned int cmd_code)
+{
+	struct mrq_sochub_mbwt_request request = {
+		.cmd = CMD_SOCHUB_MBWT_QUERY_ABI,
+		.query_abi.cmd_code = cmd_code,
+	};
+	struct tegra_bpmp_message msg = {
+		.mrq = MRQ_SOCHUB_MBWT,
+		.tx = {
+			.data = &request,
+			.size = sizeof(request),
+		},
+	};
+	int err;
+
+	err = tegra_bpmp_transfer(bpmp, &msg);
+	if (err || msg.rx.ret)
+		return false;
+
+	return true;
+}
+
+int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int *bandwidth)
+{
+	struct mrq_sochub_mbwt_request request = {
+		.cmd = CMD_SOCHUB_MBWT_GET_BW,
+		.get_bw = {
+			.instance = instance,
+			.vc_type = vc_type,
+		},
+	};
+	struct mrq_sochub_mbwt_response response = {};
+	struct tegra_bpmp_message msg = {
+		.mrq = MRQ_SOCHUB_MBWT,
+		.tx = {
+			.data = &request,
+			.size = sizeof(request),
+		},
+		.rx = {
+			.data = &response,
+			.size = sizeof(response),
+		},
+	};
+	int err;
+
+	if (!bandwidth)
+		return -EINVAL;
+
+	err = tegra_bpmp_transfer(bpmp, &msg);
+	if (err) {
+		dev_err(bpmp->dev, "MBWT get bandwidth transfer failed: %d\n",
+			err);
+		return err;
+	}
+
+	if (msg.rx.ret)
+		return msg.rx.ret;
+
+	*bandwidth = response.get_bw.bw;
+
+	return 0;
+}
+
+int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
+			unsigned int vc_type, unsigned int bandwidth)
+{
+	struct mrq_sochub_mbwt_request request = {
+		.cmd = CMD_SOCHUB_MBWT_SET_BW,
+		.set_bw = {
+			.instance = instance,
+			.vc_type = vc_type,
+			.bw = bandwidth,
+		},
+	};
+	struct tegra_bpmp_message msg = {
+		.mrq = MRQ_SOCHUB_MBWT,
+		.tx = {
+			.data = &request,
+			.size = sizeof(request),
+		},
+	};
+	int err;
+
+	err = tegra_bpmp_transfer(bpmp, &msg);
+	if (err) {
+		dev_err(bpmp->dev, "MBWT set bandwidth transfer failed: %d\n",
+			err);
+		return err;
+	}
+
+	if (msg.rx.ret)
+		return msg.rx.ret;
+
+	return 0;
+}
+
 static void tegra_bpmp_mrq_handle_ping(unsigned int mrq,
 				       struct tegra_bpmp_channel *channel,
 				       void *data)
-- 
2.43.0

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

* [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface
  2026-07-22 11:05 [PATCH v2 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
                   ` (3 preceding siblings ...)
  2026-07-22 11:05 ` [PATCH v2 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
@ 2026-07-22 11:05 ` Aniruddha Rao
  2026-07-23  5:21   ` Mikko Perttunen
  4 siblings, 1 reply; 9+ messages in thread
From: Aniruddha Rao @ 2026-07-22 11:05 UTC (permalink / raw)
  To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao

Different workloads can place different memory-bandwidth demands on the
system. Selecting an appropriate bandwidth limit depends on the runtime
workload mix and on the devices carrying that traffic, such as PCIe
traffic or GPU traffic connected over the chip-to-chip link (NVCLINK).
That information is not available to the kernel.

BPMP firmware of some Tegra platforms provides Memory Bandwidth
Throttler (MBWT) controls for PCIe and GPU traffic connected over
NVCLINK on the path to DRAM. Each PCIe bandwidth group has a single
shared cap for all traffic in that group. Bandwidth for a group can be
set per traffic type.

Add sysfs attributes on the tegra-bpmp platform device to expose a
narrow userspace interface for MBWT control. The attributes are arranged
as mbwt/pcie0..pcie5/pcie_read/bandwidth,
pcie_write/bandwidth and nvclink/bandwidth files. Each pcieN directory
identifies a PCIe bandwidth group and each traffic directory identifies
the traffic type.

Reading a bandwidth attribute queries firmware for the selected bandwidth
group and traffic type. Writing an integer programs the target bandwidth
cap in GB/s for that bandwidth group and traffic type.

Register the attributes only when BPMP firmware reports support for the
MBWT GET_BW and SET_BW requests through its query ABI.

Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
Changes since v1:
- Register the interface based on MBWT firmware support, not ACPI.
- Replace the stateful tuple attribute with per-traffic bandwidth files.
- Remove software bandwidth range checks.
- Build the sysfs interface under CONFIG_SYSFS.
- Rename the registration helper to tegra_bpmp_init_sysfs().
- Avoid platform-specific or transport-specific MBWT wording.

 .../ABI/testing/sysfs-platform-tegra-bpmp     |  48 ++++
 drivers/firmware/tegra/Makefile               |   1 +
 drivers/firmware/tegra/bpmp-private.h         |   9 +
 drivers/firmware/tegra/bpmp-tegra-sysfs.c     | 205 ++++++++++++++++++
 drivers/firmware/tegra/bpmp.c                 |   4 +
 5 files changed, 267 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-tegra-bpmp
 create mode 100644 drivers/firmware/tegra/bpmp-tegra-sysfs.c

diff --git a/Documentation/ABI/testing/sysfs-platform-tegra-bpmp b/Documentation/ABI/testing/sysfs-platform-tegra-bpmp
new file mode 100644
index 000000000000..36440e696e7d
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-tegra-bpmp
@@ -0,0 +1,48 @@
+What:		/sys/bus/platform/devices/<bpmp-device>/mbwt/pcie[0-5]/pcie_read/bandwidth
+What:		/sys/bus/platform/devices/<bpmp-device>/mbwt/pcie[0-5]/pcie_write/bandwidth
+What:		/sys/bus/platform/devices/<bpmp-device>/mbwt/pcie[0-5]/nvclink/bandwidth
+Date:		July 2026
+KernelVersion:	7.2
+Contact:	Aniruddha TVS Rao <anrao@nvidia.com>
+Description:
+		Provides access to Memory Bandwidth Throttler (MBWT)
+		controls exposed by BPMP firmware for PCIe traffic and GPU
+		traffic connected over the chip-to-chip link (NVCLINK) on
+		the path to DRAM.
+
+		The attributes are present only when BPMP firmware reports
+		support for the MBWT GET_BW and SET_BW requests through the
+		MBWT query ABI.
+
+		Each pcieN directory identifies one PCIe bandwidth group.
+		Each bandwidth group has a single shared cap for all traffic
+		in that group. A group may contain only PCIe devices, only a
+		GPU connected over NVCLINK, or both PCIe and GPU traffic in a
+		bifurcated topology.
+
+		The pcie_read, pcie_write and nvclink directories select the
+		traffic type for the selected group:
+
+		pcie_read
+			PCIe read traffic
+
+		pcie_write
+			PCIe write traffic
+
+		nvclink
+			GPU traffic connected over NVCLINK
+
+		Reading a bandwidth attribute returns the bandwidth cap in GB/s
+		reported by firmware for that bandwidth group and traffic
+		type.
+
+		Writing an integer to a bandwidth attribute programs the target
+		bandwidth cap in GB/s for that bandwidth group and traffic
+		type.
+
+		Examples:
+			cat .../mbwt/pcie0/pcie_write/bandwidth
+			echo 100 > .../mbwt/pcie0/pcie_write/bandwidth
+
+Users:		Platform integration and bandwidth tuning on systems with BPMP
+		firmware MBWT support.
diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
index 41e2e4dc31d6..59085e183fbd 100644
--- a/drivers/firmware/tegra/Makefile
+++ b/drivers/firmware/tegra/Makefile
@@ -6,5 +6,6 @@ tegra-bpmp-$(CONFIG_ARCH_TEGRA_194_SOC)	+= bpmp-tegra186.o
 tegra-bpmp-$(CONFIG_ARCH_TEGRA_234_SOC)	+= bpmp-tegra186.o
 tegra-bpmp-$(CONFIG_ARCH_TEGRA_264_SOC)	+= bpmp-tegra186.o
 tegra-bpmp-$(CONFIG_DEBUG_FS)	+= bpmp-debugfs.o
+tegra-bpmp-$(CONFIG_SYSFS)	+= bpmp-tegra-sysfs.o
 obj-$(CONFIG_TEGRA_BPMP)	+= tegra-bpmp.o
 obj-$(CONFIG_TEGRA_IVC)		+= ivc.o
diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index b3770e71020e..3a15a7aa0ffc 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -33,4 +33,13 @@ int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
 int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
 			unsigned int vc_type, unsigned int bandwidth);
 
+#ifdef CONFIG_SYSFS
+int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp);
+#else
+static inline int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp)
+{
+	return 0;
+}
+#endif
+
 #endif
diff --git a/drivers/firmware/tegra/bpmp-tegra-sysfs.c b/drivers/firmware/tegra/bpmp-tegra-sysfs.c
new file mode 100644
index 000000000000..f56836fa3314
--- /dev/null
+++ b/drivers/firmware/tegra/bpmp-tegra-sysfs.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION.
+ */
+
+#include <linux/device.h>
+#include <linux/kobject.h>
+#include <linux/kstrtox.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include <soc/tegra/bpmp.h>
+#include <soc/tegra/bpmp-abi.h>
+
+#include "bpmp-private.h"
+
+#define TEGRA_BPMP_MBWT_NUM_GROUPS	6
+#define TEGRA_BPMP_MBWT_NUM_VCS		3
+
+struct tegra_bpmp_mbwt_attr {
+	struct kobj_attribute attr;
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	unsigned int instance;
+	unsigned int vc_type;
+};
+
+struct tegra_bpmp_mbwt_sysfs {
+	struct tegra_bpmp *bpmp;
+	struct kobject *root;
+	struct kobject *group[TEGRA_BPMP_MBWT_NUM_GROUPS];
+	struct kobject *vc[TEGRA_BPMP_MBWT_NUM_GROUPS]
+			  [TEGRA_BPMP_MBWT_NUM_VCS];
+	struct tegra_bpmp_mbwt_attr attrs[TEGRA_BPMP_MBWT_NUM_GROUPS]
+				       [TEGRA_BPMP_MBWT_NUM_VCS];
+	/* Serializes bandwidth requests to firmware. */
+	struct mutex lock;
+};
+
+static const char * const tegra_bpmp_mbwt_group_names[] = {
+	"pcie0", "pcie1", "pcie2", "pcie3", "pcie4", "pcie5",
+};
+
+static const char * const tegra_bpmp_mbwt_vc_names[] = {
+	"pcie_read", "pcie_write", "nvclink",
+};
+
+static struct tegra_bpmp_mbwt_attr *
+tegra_bpmp_mbwt_attr_from_kobj_attr(struct kobj_attribute *attr)
+{
+	return container_of(attr, struct tegra_bpmp_mbwt_attr, attr);
+}
+
+static ssize_t tegra_bpmp_mbwt_show(struct kobject *kobj,
+				    struct kobj_attribute *attr, char *buf)
+{
+	struct tegra_bpmp_mbwt_attr *mbwt_attr;
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	unsigned int bandwidth;
+	int err;
+
+	mbwt_attr = tegra_bpmp_mbwt_attr_from_kobj_attr(attr);
+	mbwt = mbwt_attr->mbwt;
+
+	mutex_lock(&mbwt->lock);
+	err = tegra_bpmp_mbwt_get(mbwt->bpmp, mbwt_attr->instance,
+				  mbwt_attr->vc_type, &bandwidth);
+	mutex_unlock(&mbwt->lock);
+	if (err)
+		return err;
+
+	return sysfs_emit(buf, "%u\n", bandwidth);
+}
+
+static ssize_t tegra_bpmp_mbwt_store(struct kobject *kobj,
+				     struct kobj_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct tegra_bpmp_mbwt_attr *mbwt_attr;
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	unsigned int bandwidth;
+	int err;
+
+	err = kstrtou32(buf, 0, &bandwidth);
+	if (err)
+		return err;
+
+	mbwt_attr = tegra_bpmp_mbwt_attr_from_kobj_attr(attr);
+	mbwt = mbwt_attr->mbwt;
+
+	mutex_lock(&mbwt->lock);
+	err = tegra_bpmp_mbwt_set(mbwt->bpmp, mbwt_attr->instance,
+				  mbwt_attr->vc_type, bandwidth);
+	mutex_unlock(&mbwt->lock);
+	if (err)
+		return err;
+
+	return count;
+}
+
+static void tegra_bpmp_mbwt_sysfs_teardown(void *data)
+{
+	struct tegra_bpmp_mbwt_sysfs *mbwt = data;
+	unsigned int i, j;
+
+	for (i = 0; i < TEGRA_BPMP_MBWT_NUM_GROUPS; i++) {
+		if (!mbwt->group[i])
+			continue;
+
+		for (j = 0; j < TEGRA_BPMP_MBWT_NUM_VCS; j++) {
+			if (!mbwt->vc[i][j])
+				continue;
+
+			sysfs_remove_file(mbwt->vc[i][j],
+					  &mbwt->attrs[i][j].attr.attr);
+			kobject_put(mbwt->vc[i][j]);
+		}
+
+		kobject_put(mbwt->group[i]);
+	}
+
+	kobject_put(mbwt->root);
+}
+
+static int tegra_bpmp_mbwt_sysfs_add_group(struct tegra_bpmp_mbwt_sysfs *mbwt,
+					   unsigned int instance)
+{
+	struct tegra_bpmp_mbwt_attr *attr;
+	unsigned int vc_type;
+	int err;
+
+	mbwt->group[instance] =
+		kobject_create_and_add(tegra_bpmp_mbwt_group_names[instance],
+				       mbwt->root);
+	if (!mbwt->group[instance])
+		return -ENOMEM;
+
+	for (vc_type = 0; vc_type < TEGRA_BPMP_MBWT_NUM_VCS; vc_type++) {
+		attr = &mbwt->attrs[instance][vc_type];
+		mbwt->vc[instance][vc_type] =
+			kobject_create_and_add(tegra_bpmp_mbwt_vc_names[vc_type],
+					       mbwt->group[instance]);
+		if (!mbwt->vc[instance][vc_type])
+			return -ENOMEM;
+
+		sysfs_attr_init(&attr->attr.attr);
+		attr->attr.attr.name = "bandwidth";
+		attr->attr.attr.mode = 0644;
+		attr->attr.show = tegra_bpmp_mbwt_show;
+		attr->attr.store = tegra_bpmp_mbwt_store;
+		attr->mbwt = mbwt;
+		attr->instance = instance;
+		attr->vc_type = vc_type;
+
+		err = sysfs_create_file(mbwt->vc[instance][vc_type],
+					&attr->attr.attr);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp)
+{
+	struct tegra_bpmp_mbwt_sysfs *mbwt;
+	unsigned int instance;
+	int err;
+
+	if (!tegra_bpmp_mrq_is_supported(bpmp, MRQ_SOCHUB_MBWT))
+		return 0;
+
+	if (!tegra_bpmp_mbwt_cmd_is_supported(bpmp, CMD_SOCHUB_MBWT_GET_BW) ||
+	    !tegra_bpmp_mbwt_cmd_is_supported(bpmp, CMD_SOCHUB_MBWT_SET_BW))
+		return 0;
+
+	mbwt = devm_kzalloc(bpmp->dev, sizeof(*mbwt), GFP_KERNEL);
+	if (!mbwt)
+		return -ENOMEM;
+
+	mbwt->bpmp = bpmp;
+	mutex_init(&mbwt->lock);
+
+	mbwt->root = kobject_create_and_add("mbwt", &bpmp->dev->kobj);
+	if (!mbwt->root)
+		return -ENOMEM;
+
+	for (instance = 0; instance < TEGRA_BPMP_MBWT_NUM_GROUPS; instance++) {
+		err = tegra_bpmp_mbwt_sysfs_add_group(mbwt, instance);
+		if (err)
+			goto remove_sysfs;
+	}
+
+	err = devm_add_action_or_reset(bpmp->dev,
+				       tegra_bpmp_mbwt_sysfs_teardown, mbwt);
+	if (err)
+		return err;
+
+	return 0;
+
+remove_sysfs:
+	tegra_bpmp_mbwt_sysfs_teardown(mbwt);
+
+	return err;
+}
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 2cf8490b8b2e..2074a549a009 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -1062,6 +1062,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
 		}
 	}
 
+	err = tegra_bpmp_init_sysfs(bpmp);
+	if (err < 0)
+		dev_err(&pdev->dev, "failed to initialize sysfs: %d\n", err);
+
 	err = tegra_bpmp_init_debugfs(bpmp);
 	if (err < 0)
 		dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err);
-- 
2.43.0

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

* Re: [PATCH v2 2/5] firmware: tegra: bpmp: Add ACPI support
  2026-07-22 11:05 ` [PATCH v2 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
@ 2026-07-23  5:10   ` Mikko Perttunen
  0 siblings, 0 replies; 9+ messages in thread
From: Mikko Perttunen @ 2026-07-23  5:10 UTC (permalink / raw)
  To: thierry.reding, jonathanh, Aniruddha Rao
  Cc: linux-tegra, linux-kernel, Aniruddha Rao

On Wednesday, July 22, 2026 8:05 PM Aniruddha Rao wrote:
> Add required changes in the Tegra BPMP driver to make it compatible with
> ACPI based platforms.
> 
> On ACPI systems, IPC is handled through the AML method instead of
> the core kernel framework using mailboxes and IVC.
> 
> Bypass clock, reset and powergate init calls as these are not
> controlled by the Linux drivers on ACPI based systems.
> 
> Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
> ---
> Changes since v1:
> - Use acpi_extract_package() and a common cleanup path for BMRQ replies.
> - Treat short BMRQ responses as errors.
> - Rename the mailbox transport helper to __tegra_bpmp_transfer().
> - Avoid a dummy ACPI ops table and call SoC ops only when present.
> - Keep OF resource setup guarded by the OF node in probe.
> 
>  drivers/firmware/tegra/bpmp.c | 221 ++++++++++++++++++++++++++++------
>  1 file changed, 182 insertions(+), 39 deletions(-)
> 
> diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
> index 0eb99b1c5068..2dcb74a45b59 100644
> --- a/drivers/firmware/tegra/bpmp.c
> +++ b/drivers/firmware/tegra/bpmp.c
> @@ -3,6 +3,7 @@
>   * Copyright (c) 2016, NVIDIA CORPORATION.  All rights reserved.
>   */
>  
> +#include <linux/acpi.h>
>  #include <linux/clk/tegra.h>
>  #include <linux/genalloc.h>
>  #include <linux/mailbox_client.h>
> @@ -11,8 +12,10 @@
>  #include <linux/of_platform.h>
>  #include <linux/platform_device.h>
>  #include <linux/pm.h>
> +#include <linux/property.h>
>  #include <linux/semaphore.h>
>  #include <linux/sched/clock.h>
> +#include <linux/slab.h>
>  
>  #include <soc/tegra/bpmp.h>
>  #include <soc/tegra/bpmp-abi.h>
> @@ -23,6 +26,12 @@
>  #define MSG_ACK		BIT(0)
>  #define MSG_RING	BIT(1)
>  #define TAG_SZ		32
> +#define TEGRA_BPMP_ACPI_BMRQ_DATA_SZ	3960U
> +
> +struct tegra_bpmp_acpi_message {
> +	u64 status;
> +	u8 *data;
> +};
>  
>  static inline const struct tegra_bpmp_ops *
>  channel_to_ops(struct tegra_bpmp_channel *channel)
> @@ -343,12 +352,99 @@ static ssize_t tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel,
>  
>  static int __maybe_unused tegra_bpmp_resume(struct device *dev);
>  
> +#ifdef CONFIG_ACPI
> +static int tegra_bpmp_transfer_acpi(struct tegra_bpmp *bpmp,
> +				    struct tegra_bpmp_message *msg)
> +{
> +	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +	struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL };
> +	struct acpi_buffer format = { sizeof("NB"), "NB" };
> +	struct acpi_object_list param_list;
> +	union acpi_object params[2];
> +	struct tegra_bpmp_acpi_message *reply;
> +	acpi_status status;
> +	size_t data_len;
> +	int err = 0;
> +
> +	if (!tegra_bpmp_message_valid(msg))
> +		return -EINVAL;
> +
> +	params[0].type = ACPI_TYPE_INTEGER;
> +	params[0].integer.value = msg->mrq;
> +
> +	params[1].type = ACPI_TYPE_BUFFER;
> +	params[1].buffer.length = msg->tx.size;
> +	params[1].buffer.pointer = (u8 *)msg->tx.data;
> +
> +	param_list.count = 2;
> +	param_list.pointer = params;
> +
> +	status = acpi_evaluate_object(ACPI_HANDLE(bpmp->dev), "BMRQ",
> +				      &param_list, &output);
> +	if (ACPI_FAILURE(status)) {
> +		acpi_evaluation_failure_warn(ACPI_HANDLE(bpmp->dev), "BMRQ",
> +					     status);
> +		return -ENODEV;
> +	}
> +
> +	status = acpi_extract_package(output.pointer, &format, &response);
> +	if (ACPI_FAILURE(status)) {
> +		dev_err(bpmp->dev, "BMRQ: invalid response package: %s\n",
> +			acpi_format_exception(status));
> +		err = -ENODATA;
> +		goto out;
> +	}
> +
> +	if (response.length < sizeof(*reply)) {
> +		dev_err(bpmp->dev, "BMRQ: response too short\n");
> +		err = -ENODATA;
> +		goto out;
> +	}
> +
> +	reply = response.pointer;
> +	data_len = response.length - sizeof(*reply);
> +	if (data_len > TEGRA_BPMP_ACPI_BMRQ_DATA_SZ) {
> +		dev_err(bpmp->dev, "BMRQ: reply buffer too large (%zu)\n",
> +			data_len);
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	msg->rx.ret = (int)reply->status;
> +
> +	if (msg->rx.data && msg->rx.size) {
> +		if (data_len < msg->rx.size) {
> +			dev_err(bpmp->dev, "BMRQ: response data too short\n");
> +			err = -ENODATA;
> +			goto out;
> +		}
> +
> +		memcpy(msg->rx.data, reply->data, msg->rx.size);
> +	}
> +
> +out:
> +	kfree(response.pointer);
> +	kfree(output.pointer);
> +
> +	return err;
> +}
> +#else
> +static int tegra_bpmp_transfer_acpi(struct tegra_bpmp *bpmp,
> +				    struct tegra_bpmp_message *msg)
> +{
> +	return -EOPNOTSUPP;
> +}
> +#endif
> +
>  int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
>  			       struct tegra_bpmp_message *msg)
>  {
>  	struct tegra_bpmp_channel *channel;
>  	int err;
>  
> +	if (WARN_ON(ACPI_HANDLE(bpmp->dev)))
> +		return -EOPNOTSUPP;
> +
>  	if (WARN_ON(!irqs_disabled()))
>  		return -EPERM;
>  
> @@ -389,16 +485,13 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp,
>  }
>  EXPORT_SYMBOL_GPL(tegra_bpmp_transfer_atomic);
>  
> -int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
> -			struct tegra_bpmp_message *msg)
> +static int __tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
> +				 struct tegra_bpmp_message *msg)
>  {
>  	struct tegra_bpmp_channel *channel;
>  	unsigned long timeout;
>  	int err;
>  
> -	if (WARN_ON(irqs_disabled()))
> -		return -EPERM;
> -
>  	if (!tegra_bpmp_message_valid(msg))
>  		return -EINVAL;
>  
> @@ -428,6 +521,18 @@ int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
>  	return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size,
>  				       &msg->rx.ret);
>  }
> +
> +int tegra_bpmp_transfer(struct tegra_bpmp *bpmp,
> +			struct tegra_bpmp_message *msg)
> +{
> +	if (WARN_ON(irqs_disabled()))
> +		return -EPERM;
> +
> +	if (ACPI_HANDLE(bpmp->dev))
> +		return tegra_bpmp_transfer_acpi(bpmp, msg);
> +
> +	return __tegra_bpmp_transfer(bpmp, msg);
> +}

Having separate __tegra_bpmp_transfer and tegra_bpmp_transfer seems 
unnecessary to me. I think we can just have tegra_bpmp_transfer call 
tegra_bpmp_transfer_acpi after the tegra_bpmp_message_valid check?

>  EXPORT_SYMBOL_GPL(tegra_bpmp_transfer);
>  
>  static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp,
> @@ -606,11 +711,17 @@ static int tegra_bpmp_ping(struct tegra_bpmp *bpmp)
>  	msg.rx.data = &response;
>  	msg.rx.size = sizeof(response);
>  
> -	local_irq_save(flags);
>  	start = ktime_get();
> -	err = tegra_bpmp_transfer_atomic(bpmp, &msg);
> +
> +	if (ACPI_HANDLE(bpmp->dev)) {
> +		err = tegra_bpmp_transfer(bpmp, &msg);
> +	} else {
> +		local_irq_save(flags);
> +		err = tegra_bpmp_transfer_atomic(bpmp, &msg);
> +		local_irq_restore(flags);
> +	}
> +
>  	end = ktime_get();
> -	local_irq_restore(flags);
>  
>  	if (!err)
>  		dev_dbg(bpmp->dev,
> @@ -632,6 +743,9 @@ static int tegra_bpmp_get_firmware_tag_old(struct tegra_bpmp *bpmp, char *tag,
>  	void *virt;
>  	int err;
>  
> +	if (ACPI_HANDLE(bpmp->dev))
> +		return -EOPNOTSUPP;
> +
>  	if (size != TAG_SZ)
>  		return -EINVAL;
>  
> @@ -783,23 +897,32 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
>  	if (!bpmp)
>  		return -ENOMEM;
>  
> -	bpmp->soc = of_device_get_match_data(&pdev->dev);
> -	bpmp->dev = &pdev->dev;
> +	bpmp->soc = device_get_match_data(&pdev->dev);
> +	if (!bpmp->soc)
> +		return -EINVAL;
>  
> -	err = tegra_bpmp_init_channels(bpmp);
> -	if (err < 0)
> -		return err;
> +	bpmp->dev = &pdev->dev;
>  
>  	platform_set_drvdata(pdev, bpmp);
>  
> -	err = bpmp->soc->ops->init(bpmp);
> -	if (err < 0)
> -		return err;
> +	if (!ACPI_HANDLE(bpmp->dev)) {
> +		err = tegra_bpmp_init_channels(bpmp);
> +		if (err < 0)
> +			return err;
> +	}
>  
> -	err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
> -				     tegra_bpmp_mrq_handle_ping, bpmp);
> -	if (err < 0)
> -		goto deinit;
> +	if (bpmp->soc->ops && bpmp->soc->ops->init) {
> +		err = bpmp->soc->ops->init(bpmp);
> +		if (err < 0)
> +			return err;
> +	}
> +
> +	if (!ACPI_HANDLE(bpmp->dev)) {
> +		err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
> +					     tegra_bpmp_mrq_handle_ping, bpmp);
> +		if (err < 0)
> +			goto deinit;
> +	}
>  
>  	err = tegra_bpmp_ping(bpmp);
>  	if (err < 0) {
> @@ -815,26 +938,30 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
>  
>  	dev_info(&pdev->dev, "firmware: %.*s\n", (int)sizeof(tag), tag);
>  
> -	err = of_platform_default_populate(pdev->dev.of_node, NULL, &pdev->dev);
> -	if (err < 0)
> -		goto free_mrq;
> -
> -	if (of_property_present(pdev->dev.of_node, "#clock-cells")) {
> -		err = tegra_bpmp_init_clocks(bpmp);
> +	if (pdev->dev.of_node) {
> +		err = of_platform_default_populate(pdev->dev.of_node, NULL,
> +						   &pdev->dev);
>  		if (err < 0)
>  			goto free_mrq;
> -	}
>  
> -	if (of_property_present(pdev->dev.of_node, "#reset-cells")) {
> -		err = tegra_bpmp_init_resets(bpmp);
> -		if (err < 0)
> -			goto free_mrq;
> -	}
> +		if (of_property_present(pdev->dev.of_node, "#clock-cells")) {
> +			err = tegra_bpmp_init_clocks(bpmp);
> +			if (err < 0)
> +				goto free_mrq;
> +		}
>  
> -	if (of_property_present(pdev->dev.of_node, "#power-domain-cells")) {
> -		err = tegra_bpmp_init_powergates(bpmp);
> -		if (err < 0)
> -			goto free_mrq;
> +		if (of_property_present(pdev->dev.of_node, "#reset-cells")) {
> +			err = tegra_bpmp_init_resets(bpmp);
> +			if (err < 0)
> +				goto free_mrq;
> +		}
> +
> +		if (of_property_present(pdev->dev.of_node,
> +					"#power-domain-cells")) {
> +			err = tegra_bpmp_init_powergates(bpmp);
> +			if (err < 0)
> +				goto free_mrq;
> +		}
>  	}
>  
>  	err = tegra_bpmp_init_debugfs(bpmp);
> @@ -844,9 +971,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
>  	return 0;
>  
>  free_mrq:
> -	tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
> +	if (!ACPI_HANDLE(bpmp->dev))
> +		tegra_bpmp_free_mrq(bpmp, MRQ_PING, bpmp);
>  deinit:
> -	if (bpmp->soc->ops->deinit)
> +	if (bpmp->soc->ops && bpmp->soc->ops->deinit)
>  		bpmp->soc->ops->deinit(bpmp);
>  
>  	return err;
> @@ -867,7 +995,7 @@ static int __maybe_unused tegra_bpmp_resume(struct device *dev)
>  
>  	bpmp->suspended = false;
>  
> -	if (bpmp->soc->ops->resume)
> +	if (bpmp->soc->ops && bpmp->soc->ops->resume)
>  		return bpmp->soc->ops->resume(bpmp);
>  	else
>  		return 0;
> @@ -939,10 +1067,25 @@ static const struct of_device_id tegra_bpmp_match[] = {
>  	{ }
>  };
>  
> +#ifdef CONFIG_ACPI
> +static const struct tegra_bpmp_soc tegra_bpmp_acpi_soc = { };
> +
> +static const struct acpi_device_id tegra_bpmp_acpi_match[] = {
> +	{
> +		.id = "NVDA3001",
> +		.driver_data = (kernel_ulong_t)&tegra_bpmp_acpi_soc,
> +	},
> +	{ }
> +};
> +#endif
> +
>  static struct platform_driver tegra_bpmp_driver = {
>  	.driver = {
>  		.name = "tegra-bpmp",
>  		.of_match_table = tegra_bpmp_match,
> +#ifdef CONFIG_ACPI
> +		.acpi_match_table = tegra_bpmp_acpi_match,
> +#endif
>  		.pm = &tegra_bpmp_pm_ops,
>  		.suppress_bind_attrs = true,
>  	},
> -- 
> 2.43.0
> 




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

* Re: [PATCH v2 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers
  2026-07-22 11:05 ` [PATCH v2 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
@ 2026-07-23  5:15   ` Mikko Perttunen
  0 siblings, 0 replies; 9+ messages in thread
From: Mikko Perttunen @ 2026-07-23  5:15 UTC (permalink / raw)
  To: thierry.reding, jonathanh, Aniruddha Rao
  Cc: linux-tegra, linux-kernel, Aniruddha Rao

On Wednesday, July 22, 2026 8:05 PM Aniruddha Rao wrote:
> Add helper functions that send MBWT requests to BPMP firmware.
> 
> The helpers implement the GET_BW and SET_BW operations.
> 
> They also provide a QUERY_ABI-based probe for command availability.
> 
> Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
> ---
> Changes since v1:
> - Use generic tegra_bpmp_mbwt_*() helper names.
> - Fold the helpers into bpmp.c instead of adding a separate source file.
> - Drop SoC-specific Kconfig dependencies from the helper patch.
> - Return BPMP firmware errors directly.
> 
>  drivers/firmware/tegra/bpmp-private.h |  7 ++
>  drivers/firmware/tegra/bpmp.c         | 98 +++++++++++++++++++++++++++
>  2 files changed, 105 insertions(+)
> 
> diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
> index 07c3d46abb87..b3770e71020e 100644
> --- a/drivers/firmware/tegra/bpmp-private.h
> +++ b/drivers/firmware/tegra/bpmp-private.h
> @@ -26,4 +26,11 @@ struct tegra_bpmp_ops {
>  extern const struct tegra_bpmp_ops tegra186_bpmp_ops;
>  extern const struct tegra_bpmp_ops tegra210_bpmp_ops;
>  
> +bool tegra_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
> +				      unsigned int cmd_code);
> +int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
> +			unsigned int vc_type, unsigned int *bandwidth);
> +int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
> +			unsigned int vc_type, unsigned int bandwidth);
> +
>  #endif
> diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
> index 2dcb74a45b59..2cf8490b8b2e 100644
> --- a/drivers/firmware/tegra/bpmp.c
> +++ b/drivers/firmware/tegra/bpmp.c
> @@ -675,6 +675,104 @@ bool tegra_bpmp_mrq_is_supported(struct tegra_bpmp *bpmp, unsigned int mrq)
>  }
>  EXPORT_SYMBOL_GPL(tegra_bpmp_mrq_is_supported);
>  
> +bool tegra_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
> +				      unsigned int cmd_code)
> +{
> +	struct mrq_sochub_mbwt_request request = {
> +		.cmd = CMD_SOCHUB_MBWT_QUERY_ABI,
> +		.query_abi.cmd_code = cmd_code,
> +	};
> +	struct tegra_bpmp_message msg = {
> +		.mrq = MRQ_SOCHUB_MBWT,
> +		.tx = {
> +			.data = &request,
> +			.size = sizeof(request),
> +		},
> +	};
> +	int err;
> +
> +	err = tegra_bpmp_transfer(bpmp, &msg);
> +	if (err || msg.rx.ret)
> +		return false;
> +
> +	return true;
> +}
> +
> +int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
> +			unsigned int vc_type, unsigned int *bandwidth)
> +{
> +	struct mrq_sochub_mbwt_request request = {
> +		.cmd = CMD_SOCHUB_MBWT_GET_BW,
> +		.get_bw = {
> +			.instance = instance,
> +			.vc_type = vc_type,
> +		},
> +	};
> +	struct mrq_sochub_mbwt_response response = {};
> +	struct tegra_bpmp_message msg = {
> +		.mrq = MRQ_SOCHUB_MBWT,
> +		.tx = {
> +			.data = &request,
> +			.size = sizeof(request),
> +		},
> +		.rx = {
> +			.data = &response,
> +			.size = sizeof(response),
> +		},
> +	};
> +	int err;
> +
> +	if (!bandwidth)
> +		return -EINVAL;
> +
> +	err = tegra_bpmp_transfer(bpmp, &msg);
> +	if (err) {
> +		dev_err(bpmp->dev, "MBWT get bandwidth transfer failed: %d\n",
> +			err);
> +		return err;
> +	}
> +
> +	if (msg.rx.ret)
> +		return msg.rx.ret;

BPMP errnos are not identical to Linux errnos. We should not mix the 
two. Same for below. Return a static error code here -- if handling 
specific BPMP error codes is really necessary, they should be returned 
in a separate out-pointer.

> +
> +	*bandwidth = response.get_bw.bw;
> +
> +	return 0;
> +}
> +
> +int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
> +			unsigned int vc_type, unsigned int bandwidth)
> +{
> +	struct mrq_sochub_mbwt_request request = {
> +		.cmd = CMD_SOCHUB_MBWT_SET_BW,
> +		.set_bw = {
> +			.instance = instance,
> +			.vc_type = vc_type,
> +			.bw = bandwidth,
> +		},
> +	};
> +	struct tegra_bpmp_message msg = {
> +		.mrq = MRQ_SOCHUB_MBWT,
> +		.tx = {
> +			.data = &request,
> +			.size = sizeof(request),
> +		},
> +	};
> +	int err;
> +
> +	err = tegra_bpmp_transfer(bpmp, &msg);
> +	if (err) {
> +		dev_err(bpmp->dev, "MBWT set bandwidth transfer failed: %d\n",
> +			err);
> +		return err;
> +	}
> +
> +	if (msg.rx.ret)
> +		return msg.rx.ret;
> +
> +	return 0;
> +}
> +
>  static void tegra_bpmp_mrq_handle_ping(unsigned int mrq,
>  				       struct tegra_bpmp_channel *channel,
>  				       void *data)
> -- 
> 2.43.0
> 

Since these are only used by the sysfs code, I would squash this patch 
into the sysfs patch and move these functions into the sysfs file.




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

* Re: [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface
  2026-07-22 11:05 ` [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Aniruddha Rao
@ 2026-07-23  5:21   ` Mikko Perttunen
  0 siblings, 0 replies; 9+ messages in thread
From: Mikko Perttunen @ 2026-07-23  5:21 UTC (permalink / raw)
  To: thierry.reding, jonathanh, Aniruddha Rao
  Cc: linux-tegra, linux-kernel, Aniruddha Rao

On Wednesday, July 22, 2026 8:05 PM Aniruddha Rao wrote:
> Different workloads can place different memory-bandwidth demands on the
> system. Selecting an appropriate bandwidth limit depends on the runtime
> workload mix and on the devices carrying that traffic, such as PCIe
> traffic or GPU traffic connected over the chip-to-chip link (NVCLINK).
> That information is not available to the kernel.
> 
> BPMP firmware of some Tegra platforms provides Memory Bandwidth
> Throttler (MBWT) controls for PCIe and GPU traffic connected over
> NVCLINK on the path to DRAM. Each PCIe bandwidth group has a single
> shared cap for all traffic in that group. Bandwidth for a group can be
> set per traffic type.
> 
> Add sysfs attributes on the tegra-bpmp platform device to expose a
> narrow userspace interface for MBWT control. The attributes are arranged
> as mbwt/pcie0..pcie5/pcie_read/bandwidth,
> pcie_write/bandwidth and nvclink/bandwidth files. Each pcieN directory
> identifies a PCIe bandwidth group and each traffic directory identifies
> the traffic type.
> 
> Reading a bandwidth attribute queries firmware for the selected bandwidth
> group and traffic type. Writing an integer programs the target bandwidth
> cap in GB/s for that bandwidth group and traffic type.
> 
> Register the attributes only when BPMP firmware reports support for the
> MBWT GET_BW and SET_BW requests through its query ABI.
> 
> Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
> ---
> Changes since v1:
> - Register the interface based on MBWT firmware support, not ACPI.
> - Replace the stateful tuple attribute with per-traffic bandwidth files.
> - Remove software bandwidth range checks.
> - Build the sysfs interface under CONFIG_SYSFS.
> - Rename the registration helper to tegra_bpmp_init_sysfs().
> - Avoid platform-specific or transport-specific MBWT wording.
> 
>  .../ABI/testing/sysfs-platform-tegra-bpmp     |  48 ++++
>  drivers/firmware/tegra/Makefile               |   1 +
>  drivers/firmware/tegra/bpmp-private.h         |   9 +
>  drivers/firmware/tegra/bpmp-tegra-sysfs.c     | 205 ++++++++++++++++++
>  drivers/firmware/tegra/bpmp.c                 |   4 +
>  5 files changed, 267 insertions(+)
>  create mode 100644 Documentation/ABI/testing/sysfs-platform-tegra-bpmp
>  create mode 100644 drivers/firmware/tegra/bpmp-tegra-sysfs.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-platform-tegra-bpmp b/Documentation/ABI/testing/sysfs-platform-tegra-bpmp
> new file mode 100644
> index 000000000000..36440e696e7d
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-platform-tegra-bpmp
> @@ -0,0 +1,48 @@
> +What:		/sys/bus/platform/devices/<bpmp-device>/mbwt/pcie[0-5]/pcie_read/bandwidth
> +What:		/sys/bus/platform/devices/<bpmp-device>/mbwt/pcie[0-5]/pcie_write/bandwidth
> +What:		/sys/bus/platform/devices/<bpmp-device>/mbwt/pcie[0-5]/nvclink/bandwidth
> +Date:		July 2026
> +KernelVersion:	7.2
> +Contact:	Aniruddha TVS Rao <anrao@nvidia.com>
> +Description:
> +		Provides access to Memory Bandwidth Throttler (MBWT)
> +		controls exposed by BPMP firmware for PCIe traffic and GPU
> +		traffic connected over the chip-to-chip link (NVCLINK) on
> +		the path to DRAM.
> +
> +		The attributes are present only when BPMP firmware reports
> +		support for the MBWT GET_BW and SET_BW requests through the
> +		MBWT query ABI.
> +
> +		Each pcieN directory identifies one PCIe bandwidth group.
> +		Each bandwidth group has a single shared cap for all traffic
> +		in that group. A group may contain only PCIe devices, only a
> +		GPU connected over NVCLINK, or both PCIe and GPU traffic in a
> +		bifurcated topology.
> +
> +		The pcie_read, pcie_write and nvclink directories select the
> +		traffic type for the selected group:
> +
> +		pcie_read
> +			PCIe read traffic
> +
> +		pcie_write
> +			PCIe write traffic
> +
> +		nvclink
> +			GPU traffic connected over NVCLINK
> +
> +		Reading a bandwidth attribute returns the bandwidth cap in GB/s
> +		reported by firmware for that bandwidth group and traffic
> +		type.
> +
> +		Writing an integer to a bandwidth attribute programs the target
> +		bandwidth cap in GB/s for that bandwidth group and traffic
> +		type.
> +
> +		Examples:
> +			cat .../mbwt/pcie0/pcie_write/bandwidth
> +			echo 100 > .../mbwt/pcie0/pcie_write/bandwidth
> +
> +Users:		Platform integration and bandwidth tuning on systems with BPMP
> +		firmware MBWT support.
> diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
> index 41e2e4dc31d6..59085e183fbd 100644
> --- a/drivers/firmware/tegra/Makefile
> +++ b/drivers/firmware/tegra/Makefile
> @@ -6,5 +6,6 @@ tegra-bpmp-$(CONFIG_ARCH_TEGRA_194_SOC)	+= bpmp-tegra186.o
>  tegra-bpmp-$(CONFIG_ARCH_TEGRA_234_SOC)	+= bpmp-tegra186.o
>  tegra-bpmp-$(CONFIG_ARCH_TEGRA_264_SOC)	+= bpmp-tegra186.o
>  tegra-bpmp-$(CONFIG_DEBUG_FS)	+= bpmp-debugfs.o
> +tegra-bpmp-$(CONFIG_SYSFS)	+= bpmp-tegra-sysfs.o
>  obj-$(CONFIG_TEGRA_BPMP)	+= tegra-bpmp.o
>  obj-$(CONFIG_TEGRA_IVC)		+= ivc.o
> diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
> index b3770e71020e..3a15a7aa0ffc 100644
> --- a/drivers/firmware/tegra/bpmp-private.h
> +++ b/drivers/firmware/tegra/bpmp-private.h
> @@ -33,4 +33,13 @@ int tegra_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
>  int tegra_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
>  			unsigned int vc_type, unsigned int bandwidth);
>  
> +#ifdef CONFIG_SYSFS
> +int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp);
> +#else
> +static inline int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp)
> +{
> +	return 0;
> +}
> +#endif
> +
>  #endif
> diff --git a/drivers/firmware/tegra/bpmp-tegra-sysfs.c b/drivers/firmware/tegra/bpmp-tegra-sysfs.c

The directory already says 'tegra', so the file can be just 
'bpmp-sysfs.c'. That matches the existing 'bpmp-debugfs.c' as well.




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

end of thread, other threads:[~2026-07-23  5:21 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-22 11:05 [PATCH v2 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
2026-07-22 11:05 ` [PATCH v2 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
2026-07-22 11:05 ` [PATCH v2 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
2026-07-23  5:10   ` Mikko Perttunen
2026-07-22 11:05 ` [PATCH v2 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
2026-07-22 11:05 ` [PATCH v2 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
2026-07-23  5:15   ` Mikko Perttunen
2026-07-22 11:05 ` [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Aniruddha Rao
2026-07-23  5:21   ` Mikko Perttunen

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