* [PATCH 1/6] soc/tegra: Add Tegra410 SoC Kconfig symbol
2026-06-15 8:37 [PATCH 0/6] firmware: tegra: bpmp: Add Tegra410 ACPI MBWT support Aniruddha Rao
@ 2026-06-15 8:37 ` Aniruddha Rao
2026-07-10 9:34 ` Thierry Reding
2026-06-15 8:37 ` [PATCH 2/6] firmware: tegra: bpmp: Move channel, resource init to helper Aniruddha Rao
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Aniruddha Rao @ 2026-06-15 8:37 UTC (permalink / raw)
To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao
Add a dedicated ARCH_TEGRA_410_SOC symbol in the Tegra SoC Kconfig so
Tegra410-specific support can be selected independently.
The Tegra BPMP driver will use this symbol to build Tegra410-specific
BPMP support, including the Tegra410 Memory Bandwidth Throttler helpers,
without enabling that code for other Tegra SoCs.
Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
drivers/soc/tegra/Kconfig | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
index 073346c1542b..72b7f5db8179 100644
--- a/drivers/soc/tegra/Kconfig
+++ b/drivers/soc/tegra/Kconfig
@@ -149,6 +149,16 @@ config ARCH_TEGRA_241_SOC
help
Enable support for the NVIDIA Tegra241 SoC.
+config ARCH_TEGRA_410_SOC
+ bool "NVIDIA Tegra410 SoC"
+ help
+ Enable support for the NVIDIA Tegra410 SoC.
+
+ This option enables Tegra410-specific platform support in
+ drivers shared across NVIDIA Tegra SoCs.
+ It should be selected when building a kernel for Tegra410
+ systems.
+
config ARCH_TEGRA_264_SOC
bool "NVIDIA Tegra264 SoC"
default ARCH_TEGRA
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH 1/6] soc/tegra: Add Tegra410 SoC Kconfig symbol
2026-06-15 8:37 ` [PATCH 1/6] soc/tegra: Add Tegra410 SoC Kconfig symbol Aniruddha Rao
@ 2026-07-10 9:34 ` Thierry Reding
0 siblings, 0 replies; 13+ messages in thread
From: Thierry Reding @ 2026-07-10 9:34 UTC (permalink / raw)
To: Aniruddha Rao; +Cc: thierry.reding, jonathanh, linux-tegra, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1742 bytes --]
On Mon, Jun 15, 2026 at 08:37:26AM +0000, Aniruddha Rao wrote:
> Add a dedicated ARCH_TEGRA_410_SOC symbol in the Tegra SoC Kconfig so
> Tegra410-specific support can be selected independently.
>
> The Tegra BPMP driver will use this symbol to build Tegra410-specific
> BPMP support, including the Tegra410 Memory Bandwidth Throttler helpers,
> without enabling that code for other Tegra SoCs.
That paragraph is not useful. We want this in general for Tegra410, any
specific features are irrelevant at this point.
>
> Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
> ---
> drivers/soc/tegra/Kconfig | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
> index 073346c1542b..72b7f5db8179 100644
> --- a/drivers/soc/tegra/Kconfig
> +++ b/drivers/soc/tegra/Kconfig
> @@ -149,6 +149,16 @@ config ARCH_TEGRA_241_SOC
> help
> Enable support for the NVIDIA Tegra241 SoC.
>
> +config ARCH_TEGRA_410_SOC
> + bool "NVIDIA Tegra410 SoC"
> + help
> + Enable support for the NVIDIA Tegra410 SoC.
> +
> + This option enables Tegra410-specific platform support in
> + drivers shared across NVIDIA Tegra SoCs.
> + It should be selected when building a kernel for Tegra410
> + systems.
> +
> config ARCH_TEGRA_264_SOC
> bool "NVIDIA Tegra264 SoC"
> default ARCH_TEGRA
We have traditionally sorted these chronologically (with some exceptions
like Tegra238 which is sorted after Tegra234 because it's from the same
family), so this should go after Tegra264. There's also some benefit in
grouping this with Tegra241, so I suppose we could move both into a
separate section to highlight their relationship.
Thierry
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 2/6] firmware: tegra: bpmp: Move channel, resource init to helper
2026-06-15 8:37 [PATCH 0/6] firmware: tegra: bpmp: Add Tegra410 ACPI MBWT support Aniruddha Rao
2026-06-15 8:37 ` [PATCH 1/6] soc/tegra: Add Tegra410 SoC Kconfig symbol Aniruddha Rao
@ 2026-06-15 8:37 ` Aniruddha Rao
2026-07-10 9:39 ` Thierry Reding
2026-06-15 8:37 ` [PATCH 3/6] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Aniruddha Rao @ 2026-06-15 8:37 UTC (permalink / raw)
To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao
Refactor the BPMP driver by moving channel initialization and Device Tree
resource parsing into separate helper functions.
This prepares the driver for ACPI support, where these helpers will be
skipped because channel initialization is handled by ACPI AML methods on
ACPI-based systems.
Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
drivers/firmware/tegra/bpmp.c | 94 +++++++++++++++++++++--------------
1 file changed, 57 insertions(+), 37 deletions(-)
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 753472b53bd8..16ca0d104c1d 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,37 +745,85 @@ 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),
+ 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;
- platform_set_drvdata(pdev, bpmp);
+ return 0;
+}
+
+static int tegra_bpmp_init_resources(struct tegra_bpmp *bpmp)
+{
+ int err;
+
+ err = of_platform_default_populate(bpmp->dev->of_node, NULL, bpmp->dev);
+ if (err < 0)
+ return err;
+
+ if (of_property_present(bpmp->dev->of_node, "#clock-cells")) {
+ err = tegra_bpmp_init_clocks(bpmp);
+ if (err < 0)
+ return err;
+ }
+
+ if (of_property_present(bpmp->dev->of_node, "#reset-cells")) {
+ err = tegra_bpmp_init_resets(bpmp);
+ if (err < 0)
+ return err;
+ }
+
+ if (of_property_present(bpmp->dev->of_node, "#power-domain-cells"))
+ err = tegra_bpmp_init_powergates(bpmp);
+
+ return err;
+}
+
+static int tegra_bpmp_probe(struct platform_device *pdev)
+{
+ struct tegra_bpmp *bpmp;
+ char tag[TAG_SZ];
+ int err;
- err = bpmp->soc->ops->init(bpmp);
+ bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
+ if (!bpmp)
+ return -ENOMEM;
+
+ bpmp->soc = 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);
+
+ if (bpmp->soc->ops->init) {
+ err = bpmp->soc->ops->init(bpmp);
+ if (err < 0)
+ return err;
+ }
+
err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
tegra_bpmp_mrq_handle_ping, bpmp);
if (err < 0)
@@ -805,28 +843,10 @@ 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);
+ err = tegra_bpmp_init_resources(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, "#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);
if (err < 0)
dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH 2/6] firmware: tegra: bpmp: Move channel, resource init to helper
2026-06-15 8:37 ` [PATCH 2/6] firmware: tegra: bpmp: Move channel, resource init to helper Aniruddha Rao
@ 2026-07-10 9:39 ` Thierry Reding
0 siblings, 0 replies; 13+ messages in thread
From: Thierry Reding @ 2026-07-10 9:39 UTC (permalink / raw)
To: Aniruddha Rao; +Cc: thierry.reding, jonathanh, linux-tegra, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 5982 bytes --]
On Mon, Jun 15, 2026 at 08:37:27AM +0000, Aniruddha Rao wrote:
> Refactor the BPMP driver by moving channel initialization and Device Tree
> resource parsing into separate helper functions.
>
> This prepares the driver for ACPI support, where these helpers will be
> skipped because channel initialization is handled by ACPI AML methods on
> ACPI-based systems.
>
> Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
> ---
> drivers/firmware/tegra/bpmp.c | 94 +++++++++++++++++++++--------------
> 1 file changed, 57 insertions(+), 37 deletions(-)
>
> diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
> index 753472b53bd8..16ca0d104c1d 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,37 +745,85 @@ 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),
> + 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;
>
> - platform_set_drvdata(pdev, bpmp);
> + return 0;
> +}
> +
> +static int tegra_bpmp_init_resources(struct tegra_bpmp *bpmp)
> +{
> + int err;
> +
> + err = of_platform_default_populate(bpmp->dev->of_node, NULL, bpmp->dev);
> + if (err < 0)
> + return err;
This doesn't really belong in tegra_bpmp_init_resources(). Instead I
think we should keep this separate and wrap the calls to the OF-specific
functions in an OF conditional.
Come to think of it, looking at subsequent patches the OF conditional
seems to have been the prime motivator for moving this into a separate
function. And if we're moving of_platform_default_populate() out of this
there's very little reason left to do so. I think we should just keep
these calls in tegra_bpmp_probe() and wrap them in the conditional
instead. Should make the patch a bit easier to understand, too.
Thierry
> +
> + if (of_property_present(bpmp->dev->of_node, "#clock-cells")) {
> + err = tegra_bpmp_init_clocks(bpmp);
> + if (err < 0)
> + return err;
> + }
> +
> + if (of_property_present(bpmp->dev->of_node, "#reset-cells")) {
> + err = tegra_bpmp_init_resets(bpmp);
> + if (err < 0)
> + return err;
> + }
> +
> + if (of_property_present(bpmp->dev->of_node, "#power-domain-cells"))
> + err = tegra_bpmp_init_powergates(bpmp);
> +
> + return err;
> +}
> +
> +static int tegra_bpmp_probe(struct platform_device *pdev)
> +{
> + struct tegra_bpmp *bpmp;
> + char tag[TAG_SZ];
> + int err;
>
> - err = bpmp->soc->ops->init(bpmp);
> + bpmp = devm_kzalloc(&pdev->dev, sizeof(*bpmp), GFP_KERNEL);
> + if (!bpmp)
> + return -ENOMEM;
> +
> + bpmp->soc = 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);
> +
> + if (bpmp->soc->ops->init) {
Maybe add in a check for bpmp->soc->ops in here, so that we don't have
to assign an empty ops structure for ACPI if it isn't needed.
> + err = bpmp->soc->ops->init(bpmp);
> + if (err < 0)
> + return err;
> + }
> +
> err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
> tegra_bpmp_mrq_handle_ping, bpmp);
> if (err < 0)
> @@ -805,28 +843,10 @@ 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);
> + err = tegra_bpmp_init_resources(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, "#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);
> if (err < 0)
> dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err);
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 3/6] firmware: tegra: bpmp: Add ACPI support
2026-06-15 8:37 [PATCH 0/6] firmware: tegra: bpmp: Add Tegra410 ACPI MBWT support Aniruddha Rao
2026-06-15 8:37 ` [PATCH 1/6] soc/tegra: Add Tegra410 SoC Kconfig symbol Aniruddha Rao
2026-06-15 8:37 ` [PATCH 2/6] firmware: tegra: bpmp: Move channel, resource init to helper Aniruddha Rao
@ 2026-06-15 8:37 ` Aniruddha Rao
2026-07-10 10:08 ` Thierry Reding
2026-06-15 8:37 ` [PATCH 4/6] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Aniruddha Rao @ 2026-06-15 8:37 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.
Keep the existing TEGRA_BPMP Kconfig dependency on TEGRA_HSP_MBOX
and the TEGRA_IVC selection unchanged. The driver remains a single
implementation shared by Device Tree and ACPI platforms, and the
Device Tree mailbox/IVC transport is still built unconditionally. ACPI
systems bypass channel and resource initialization, MRQ registration,
and mailbox transfers at runtime while the existing Device Tree
transport and build model remain unchanged.
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>
---
drivers/firmware/tegra/bpmp-private.h | 9 ++
drivers/firmware/tegra/bpmp.c | 178 ++++++++++++++++++++++++--
2 files changed, 173 insertions(+), 14 deletions(-)
diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index 07c3d46abb87..ebde0e36ae20 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -26,4 +26,13 @@ struct tegra_bpmp_ops {
extern const struct tegra_bpmp_ops tegra186_bpmp_ops;
extern const struct tegra_bpmp_ops tegra210_bpmp_ops;
+/* Maximum ACPI BPMP mailbox data buffer size. */
+#define TEGRA_BPMP_ACPI_BMRQ_DATA_SZ 3960U
+
+struct tegra_bpmp_acpi_message {
+ u64 status;
+ u8 *data_ptr;
+ u8 data[TEGRA_BPMP_ACPI_BMRQ_DATA_SZ];
+};
+
#endif
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 16ca0d104c1d..e9c0d6d3e24d 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>
@@ -13,6 +14,7 @@
#include <linux/pm.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>
@@ -343,12 +345,113 @@ 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)
+{
+ acpi_status status;
+ union acpi_object params[2];
+ struct acpi_object_list param_list;
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ struct tegra_bpmp_acpi_message *pkg;
+ struct acpi_buffer format = { sizeof("NB"), "NB" };
+ struct acpi_buffer extract;
+ size_t rbuf_len, rdata_len, rx_size;
+
+ 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",
+ ¶m_list, &output);
+ if (ACPI_FAILURE(status)) {
+ acpi_evaluation_failure_warn(ACPI_HANDLE(bpmp->dev), "BMRQ",
+ status);
+ kfree(output.pointer);
+ return -ENODEV;
+ }
+
+ obj = (union acpi_object *)output.pointer;
+ /* Validate returned type */
+ if (!obj || obj->type != ACPI_TYPE_PACKAGE) {
+ dev_err(bpmp->dev, "Invalid BMRQ data\n");
+ kfree(output.pointer);
+ return -ENODATA;
+ }
+
+ if (obj->package.count < 2 ||
+ obj->package.elements[1].type != ACPI_TYPE_BUFFER) {
+ dev_err(bpmp->dev, "Invalid BMRQ data\n");
+ kfree(output.pointer);
+ return -ENODATA;
+ }
+
+ rdata_len = obj->package.elements[1].buffer.length;
+ rbuf_len = sizeof(u64) + sizeof(u8 *) + rdata_len;
+ if (rbuf_len > sizeof(*pkg)) {
+ dev_err(bpmp->dev, "BMRQ: reply buffer too large (%zu)\n", rbuf_len);
+ kfree(output.pointer);
+ return -EINVAL;
+ }
+
+ pkg = kzalloc(sizeof(*pkg), GFP_KERNEL);
+ if (!pkg) {
+ kfree(output.pointer);
+ return -ENOMEM;
+ }
+
+ extract.length = rbuf_len;
+ extract.pointer = pkg;
+
+ status = acpi_extract_package(obj, &format, &extract);
+ if (ACPI_FAILURE(status)) {
+ dev_err(bpmp->dev, "BMRQ: failed to parse package (%s)\n",
+ acpi_format_exception(status));
+ kfree(pkg);
+ kfree(output.pointer);
+ return -EINVAL;
+ }
+
+ msg->rx.ret = (int)pkg->status;
+ if (msg->rx.data && msg->rx.size) {
+ rx_size = min_t(size_t, msg->rx.size, rdata_len);
+
+ memset(msg->rx.data, 0, msg->rx.size);
+ memcpy(msg->rx.data, pkg->data, rx_size);
+ }
+
+ /* Free memory allocated by ACPI core */
+ kfree(pkg);
+ kfree(output.pointer);
+ return 0;
+}
+#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 +492,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_channel(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 +528,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);
+ else
+ return tegra_bpmp_transfer_channel(bpmp, msg);
+}
EXPORT_SYMBOL_GPL(tegra_bpmp_transfer);
static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp,
@@ -506,6 +618,9 @@ int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq,
struct tegra_bpmp_mrq *entry;
unsigned long flags;
+ if (ACPI_HANDLE(bpmp->dev))
+ return -EOPNOTSUPP;
+
if (!handler)
return -EINVAL;
@@ -531,6 +646,9 @@ void tegra_bpmp_free_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, void *data)
struct tegra_bpmp_mrq *entry;
unsigned long flags;
+ if (ACPI_HANDLE(bpmp->dev))
+ return;
+
spin_lock_irqsave(&bpmp->lock, flags);
entry = tegra_bpmp_find_mrq(bpmp, mrq);
@@ -606,11 +724,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);
- end = ktime_get();
- local_irq_restore(flags);
+ if (ACPI_HANDLE(bpmp->dev)) {
+ start = ktime_get();
+ err = tegra_bpmp_transfer_acpi(bpmp, &msg);
+ end = ktime_get();
+ } else {
+ local_irq_save(flags);
+ start = ktime_get();
+ err = tegra_bpmp_transfer_atomic(bpmp, &msg);
+ end = ktime_get();
+ local_irq_restore(flags);
+ }
if (!err)
dev_dbg(bpmp->dev,
@@ -635,6 +759,9 @@ static int tegra_bpmp_get_firmware_tag_old(struct tegra_bpmp *bpmp, char *tag,
if (size != TAG_SZ)
return -EINVAL;
+ if (ACPI_HANDLE(bpmp->dev))
+ return -EOPNOTSUPP;
+
virt = dma_alloc_coherent(bpmp->dev, TAG_SZ, &phys,
GFP_KERNEL | GFP_DMA32);
if (!virt)
@@ -737,6 +864,9 @@ static int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
{
size_t size;
+ if (ACPI_HANDLE(bpmp->dev))
+ return 0;
+
INIT_LIST_HEAD(&bpmp->mrqs);
spin_lock_init(&bpmp->lock);
@@ -777,6 +907,9 @@ static int tegra_bpmp_init_resources(struct tegra_bpmp *bpmp)
{
int err;
+ if (!bpmp->dev->of_node)
+ return 0;
+
err = of_platform_default_populate(bpmp->dev->of_node, NULL, bpmp->dev);
if (err < 0)
return err;
@@ -824,10 +957,12 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
return err;
}
- err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
- tegra_bpmp_mrq_handle_ping, bpmp);
- if (err < 0)
- goto deinit;
+ 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) {
@@ -949,10 +1084,25 @@ static const struct of_device_id tegra_bpmp_match[] = {
{ }
};
+static const struct tegra_bpmp_ops tegra_bpmp_acpi_ops = { };
+
+static const struct tegra_bpmp_soc tegra_bpmp_acpi_soc = {
+ .ops = &tegra_bpmp_acpi_ops,
+};
+
+static const struct acpi_device_id tegra_bpmp_acpi_match[] = {
+ {
+ .id = "NVDA3001",
+ .driver_data = (kernel_ulong_t)&tegra_bpmp_acpi_soc,
+ },
+ { }
+};
+
static struct platform_driver tegra_bpmp_driver = {
.driver = {
.name = "tegra-bpmp",
.of_match_table = tegra_bpmp_match,
+ .acpi_match_table = tegra_bpmp_acpi_match,
.pm = &tegra_bpmp_pm_ops,
.suppress_bind_attrs = true,
},
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH 3/6] firmware: tegra: bpmp: Add ACPI support
2026-06-15 8:37 ` [PATCH 3/6] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
@ 2026-07-10 10:08 ` Thierry Reding
0 siblings, 0 replies; 13+ messages in thread
From: Thierry Reding @ 2026-07-10 10:08 UTC (permalink / raw)
To: Aniruddha Rao; +Cc: thierry.reding, jonathanh, linux-tegra, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 11718 bytes --]
On Mon, Jun 15, 2026 at 08:37:28AM +0000, 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.
>
> Keep the existing TEGRA_BPMP Kconfig dependency on TEGRA_HSP_MBOX
> and the TEGRA_IVC selection unchanged. The driver remains a single
> implementation shared by Device Tree and ACPI platforms, and the
> Device Tree mailbox/IVC transport is still built unconditionally. ACPI
> systems bypass channel and resource initialization, MRQ registration,
> and mailbox transfers at runtime while the existing Device Tree
> transport and build model remain unchanged.
That entire paragraph is not needed. If you really want to, you can move
the bits about what's being bypassed into the paragraph below, but
generally don't describe what has not changed. Describe what's changed
and why.
>
> 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>
> ---
> drivers/firmware/tegra/bpmp-private.h | 9 ++
> drivers/firmware/tegra/bpmp.c | 178 ++++++++++++++++++++++++--
> 2 files changed, 173 insertions(+), 14 deletions(-)
>
> diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
> index 07c3d46abb87..ebde0e36ae20 100644
> --- a/drivers/firmware/tegra/bpmp-private.h
> +++ b/drivers/firmware/tegra/bpmp-private.h
> @@ -26,4 +26,13 @@ struct tegra_bpmp_ops {
> extern const struct tegra_bpmp_ops tegra186_bpmp_ops;
> extern const struct tegra_bpmp_ops tegra210_bpmp_ops;
>
> +/* Maximum ACPI BPMP mailbox data buffer size. */
> +#define TEGRA_BPMP_ACPI_BMRQ_DATA_SZ 3960U
> +
> +struct tegra_bpmp_acpi_message {
> + u64 status;
> + u8 *data_ptr;
> + u8 data[TEGRA_BPMP_ACPI_BMRQ_DATA_SZ];
> +};
> +
> #endif
> diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
> index 16ca0d104c1d..e9c0d6d3e24d 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>
> @@ -13,6 +14,7 @@
> #include <linux/pm.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>
> @@ -343,12 +345,113 @@ 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)
> +{
> + acpi_status status;
> + union acpi_object params[2];
> + struct acpi_object_list param_list;
> + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> + union acpi_object *obj;
> + struct tegra_bpmp_acpi_message *pkg;
> + struct acpi_buffer format = { sizeof("NB"), "NB" };
> + struct acpi_buffer extract;
> + size_t rbuf_len, rdata_len, rx_size;
> +
> + 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",
> + ¶m_list, &output);
> + if (ACPI_FAILURE(status)) {
> + acpi_evaluation_failure_warn(ACPI_HANDLE(bpmp->dev), "BMRQ",
> + status);
> + kfree(output.pointer);
> + return -ENODEV;
> + }
> +
> + obj = (union acpi_object *)output.pointer;
> + /* Validate returned type */
> + if (!obj || obj->type != ACPI_TYPE_PACKAGE) {
> + dev_err(bpmp->dev, "Invalid BMRQ data\n");
> + kfree(output.pointer);
> + return -ENODATA;
> + }
> +
> + if (obj->package.count < 2 ||
> + obj->package.elements[1].type != ACPI_TYPE_BUFFER) {
> + dev_err(bpmp->dev, "Invalid BMRQ data\n");
> + kfree(output.pointer);
> + return -ENODATA;
> + }
These two blocks of checks seem redundant because acpi_extract_package()
already checks for them.
> +
> + rdata_len = obj->package.elements[1].buffer.length;
> + rbuf_len = sizeof(u64) + sizeof(u8 *) + rdata_len;
> + if (rbuf_len > sizeof(*pkg)) {
> + dev_err(bpmp->dev, "BMRQ: reply buffer too large (%zu)\n", rbuf_len);
> + kfree(output.pointer);
> + return -EINVAL;
> + }
> +
> + pkg = kzalloc(sizeof(*pkg), GFP_KERNEL);
> + if (!pkg) {
> + kfree(output.pointer);
> + return -ENOMEM;
> + }
Given the number of times we have to repeate kfree(output.pointer) it
might be worth extracting this into an error unwind path. It's not going
to reduce the number of lines because we need to assign err = ECODE, but
still worth it, in my opinion.
> +
> + extract.length = rbuf_len;
> + extract.pointer = pkg;
> +
> + status = acpi_extract_package(obj, &format, &extract);
> + if (ACPI_FAILURE(status)) {
> + dev_err(bpmp->dev, "BMRQ: failed to parse package (%s)\n",
> + acpi_format_exception(status));
> + kfree(pkg);
> + kfree(output.pointer);
> + return -EINVAL;
> + }
> +
> + msg->rx.ret = (int)pkg->status;
> + if (msg->rx.data && msg->rx.size) {
> + rx_size = min_t(size_t, msg->rx.size, rdata_len);
> +
> + memset(msg->rx.data, 0, msg->rx.size);
Why is this necessary? When would we expect to be copying less memory
than we asked for. Should that be considered an error?
> + memcpy(msg->rx.data, pkg->data, rx_size);
> + }
> +
> + /* Free memory allocated by ACPI core */
> + kfree(pkg);
> + kfree(output.pointer);
> + return 0;
> +}
> +#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 +492,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_channel(struct tegra_bpmp *bpmp,
> + struct tegra_bpmp_message *msg)
I'd prefer something like a __ prefix rather than the _channel suffix
which isn't really meaningful in this context.
> {
> 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 +528,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);
> + else
> + return tegra_bpmp_transfer_channel(bpmp, msg);
> +}
> EXPORT_SYMBOL_GPL(tegra_bpmp_transfer);
>
> static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp,
> @@ -506,6 +618,9 @@ int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq,
> struct tegra_bpmp_mrq *entry;
> unsigned long flags;
>
> + if (ACPI_HANDLE(bpmp->dev))
> + return -EOPNOTSUPP;
> +
> if (!handler)
> return -EINVAL;
>
> @@ -531,6 +646,9 @@ void tegra_bpmp_free_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, void *data)
> struct tegra_bpmp_mrq *entry;
> unsigned long flags;
>
> + if (ACPI_HANDLE(bpmp->dev))
> + return;
> +
> spin_lock_irqsave(&bpmp->lock, flags);
>
> entry = tegra_bpmp_find_mrq(bpmp, mrq);
> @@ -606,11 +724,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);
> - end = ktime_get();
> - local_irq_restore(flags);
> + if (ACPI_HANDLE(bpmp->dev)) {
> + start = ktime_get();
> + err = tegra_bpmp_transfer_acpi(bpmp, &msg);
> + end = ktime_get();
> + } else {
> + local_irq_save(flags);
> + start = ktime_get();
> + err = tegra_bpmp_transfer_atomic(bpmp, &msg);
> + end = ktime_get();
> + local_irq_restore(flags);
> + }
>
> if (!err)
> dev_dbg(bpmp->dev,
> @@ -635,6 +759,9 @@ static int tegra_bpmp_get_firmware_tag_old(struct tegra_bpmp *bpmp, char *tag,
> if (size != TAG_SZ)
> return -EINVAL;
>
> + if (ACPI_HANDLE(bpmp->dev))
> + return -EOPNOTSUPP;
Might as well move this check up before the size check so we don't waste
the extra comparison if we know the operation isn't supported anyway.
> +
> virt = dma_alloc_coherent(bpmp->dev, TAG_SZ, &phys,
> GFP_KERNEL | GFP_DMA32);
> if (!virt)
> @@ -737,6 +864,9 @@ static int tegra_bpmp_init_channels(struct tegra_bpmp *bpmp)
> {
> size_t size;
>
> + if (ACPI_HANDLE(bpmp->dev))
> + return 0;
> +
> INIT_LIST_HEAD(&bpmp->mrqs);
> spin_lock_init(&bpmp->lock);
>
> @@ -777,6 +907,9 @@ static int tegra_bpmp_init_resources(struct tegra_bpmp *bpmp)
> {
> int err;
>
> + if (!bpmp->dev->of_node)
> + return 0;
> +
Like I mentioned earlier, I think we can drop this check (and the
ACPI_HANDLE check in tegra_bpmp_init_channels() above) and just move the
conditional to tegra_bpmp_probe() instead.
> err = of_platform_default_populate(bpmp->dev->of_node, NULL, bpmp->dev);
> if (err < 0)
> return err;
> @@ -824,10 +957,12 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
> return err;
> }
>
> - err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
> - tegra_bpmp_mrq_handle_ping, bpmp);
> - if (err < 0)
> - goto deinit;
> + if (!ACPI_HANDLE(bpmp->dev)) {
> + err = tegra_bpmp_request_mrq(bpmp, MRQ_PING,
> + tegra_bpmp_mrq_handle_ping, bpmp);
> + if (err < 0)
> + goto deinit;
> + }
This can then go into the same conditional.
>
> err = tegra_bpmp_ping(bpmp);
> if (err < 0) {
> @@ -949,10 +1084,25 @@ static const struct of_device_id tegra_bpmp_match[] = {
> { }
> };
>
> +static const struct tegra_bpmp_ops tegra_bpmp_acpi_ops = { };
> +
> +static const struct tegra_bpmp_soc tegra_bpmp_acpi_soc = {
> + .ops = &tegra_bpmp_acpi_ops,
> +};
This empty tegra_bpmp_ops struct is useless. Just add checks where
appropriate to make it optional.
Thierry
> +
> +static const struct acpi_device_id tegra_bpmp_acpi_match[] = {
> + {
> + .id = "NVDA3001",
> + .driver_data = (kernel_ulong_t)&tegra_bpmp_acpi_soc,
> + },
> + { }
> +};
> +
> static struct platform_driver tegra_bpmp_driver = {
> .driver = {
> .name = "tegra-bpmp",
> .of_match_table = tegra_bpmp_match,
> + .acpi_match_table = tegra_bpmp_acpi_match,
> .pm = &tegra_bpmp_pm_ops,
> .suppress_bind_attrs = true,
> },
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 4/6] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions
2026-06-15 8:37 [PATCH 0/6] firmware: tegra: bpmp: Add Tegra410 ACPI MBWT support Aniruddha Rao
` (2 preceding siblings ...)
2026-06-15 8:37 ` [PATCH 3/6] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
@ 2026-06-15 8:37 ` Aniruddha Rao
2026-07-10 10:13 ` Thierry Reding
2026-06-15 8:37 ` [PATCH 5/6] firmware: tegra: bpmp: Add Tegra410 MBWT BPMP helpers Aniruddha Rao
2026-06-15 8:37 ` [PATCH 6/6] firmware: tegra: bpmp: Add Tegra410 MBWT sysfs interface Aniruddha Rao
5 siblings, 1 reply; 13+ messages in thread
From: Aniruddha Rao @ 2026-06-15 8:37 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. Tegra410 provides Memory Bandwidth Throttler (MBWT) controls for
PCIe and GPU connected over chip-to-chip link (Nv-Clink) 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>
---
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..0660b1d669ef 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 (Nv-Clink)
+ */
+ 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 (Nv-Clink)
+ */
+ 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] 13+ messages in thread* [PATCH 5/6] firmware: tegra: bpmp: Add Tegra410 MBWT BPMP helpers
2026-06-15 8:37 [PATCH 0/6] firmware: tegra: bpmp: Add Tegra410 ACPI MBWT support Aniruddha Rao
` (3 preceding siblings ...)
2026-06-15 8:37 ` [PATCH 4/6] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
@ 2026-06-15 8:37 ` Aniruddha Rao
2026-07-10 10:19 ` Thierry Reding
2026-06-15 8:37 ` [PATCH 6/6] firmware: tegra: bpmp: Add Tegra410 MBWT sysfs interface Aniruddha Rao
5 siblings, 1 reply; 13+ messages in thread
From: Aniruddha Rao @ 2026-06-15 8:37 UTC (permalink / raw)
To: thierry.reding, jonathanh; +Cc: linux-tegra, linux-kernel, Aniruddha Rao
Add helper functions that send MBWT requests to BPMP firmware on
Tegra410.
The helpers implement the GET_BW and SET_BW operations and provide a
QUERY_ABI-based probe for command availability.
Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
---
drivers/firmware/tegra/Makefile | 1 +
drivers/firmware/tegra/bpmp-private.h | 31 +++++++
drivers/firmware/tegra/bpmp-tegra410.c | 108 +++++++++++++++++++++++++
3 files changed, 140 insertions(+)
create mode 100644 drivers/firmware/tegra/bpmp-tegra410.c
diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
index 41e2e4dc31d6..4310cc0ff294 100644
--- a/drivers/firmware/tegra/Makefile
+++ b/drivers/firmware/tegra/Makefile
@@ -4,6 +4,7 @@ tegra-bpmp-$(CONFIG_ARCH_TEGRA_210_SOC) += bpmp-tegra210.o
tegra-bpmp-$(CONFIG_ARCH_TEGRA_186_SOC) += bpmp-tegra186.o
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_410_SOC) += bpmp-tegra410.o
tegra-bpmp-$(CONFIG_ARCH_TEGRA_264_SOC) += bpmp-tegra186.o
tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o
obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o
diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index ebde0e36ae20..c3f466ae5979 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -35,4 +35,35 @@ struct tegra_bpmp_acpi_message {
u8 data[TEGRA_BPMP_ACPI_BMRQ_DATA_SZ];
};
+#if IS_ENABLED(CONFIG_ARCH_TEGRA_410_SOC)
+int tegra410_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
+ unsigned int vc_type, unsigned int bandwidth);
+int tegra410_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
+ unsigned int vc_type, unsigned int *bandwidth_out);
+bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
+ unsigned int cmd_code);
+#else
+static inline int tegra410_bpmp_mbwt_set(struct tegra_bpmp *bpmp,
+ unsigned int instance,
+ unsigned int vc_type,
+ unsigned int bandwidth)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int tegra410_bpmp_mbwt_get(struct tegra_bpmp *bpmp,
+ unsigned int instance,
+ unsigned int vc_type,
+ unsigned int *bandwidth_out)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
+ unsigned int cmd_code)
+{
+ return false;
+}
+#endif
+
#endif
diff --git a/drivers/firmware/tegra/bpmp-tegra410.c b/drivers/firmware/tegra/bpmp-tegra410.c
new file mode 100644
index 000000000000..87c5dfab864b
--- /dev/null
+++ b/drivers/firmware/tegra/bpmp-tegra410.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION.
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include <soc/tegra/bpmp.h>
+#include <soc/tegra/bpmp-abi.h>
+
+#include "bpmp-private.h"
+
+bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
+ unsigned int cmd_code)
+{
+ struct mrq_sochub_mbwt_request request;
+ struct tegra_bpmp_message msg;
+ int err;
+
+ memset(&request, 0, sizeof(request));
+ request.cmd = CMD_SOCHUB_MBWT_QUERY_ABI;
+ request.query_abi.cmd_code = cmd_code;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.mrq = MRQ_SOCHUB_MBWT;
+ msg.tx.data = &request;
+ msg.tx.size = sizeof(request);
+
+ err = tegra_bpmp_transfer(bpmp, &msg);
+ if (err || msg.rx.ret)
+ return false;
+
+ return true;
+}
+
+int tegra410_bpmp_mbwt_set(struct tegra_bpmp *bpmp,
+ unsigned int instance,
+ unsigned int vc_type,
+ unsigned int bandwidth)
+{
+ struct mrq_sochub_mbwt_request request;
+ struct tegra_bpmp_message msg;
+ int err;
+
+ memset(&request, 0, sizeof(request));
+ request.cmd = CMD_SOCHUB_MBWT_SET_BW;
+ request.set_bw.instance = instance;
+ request.set_bw.vc_type = vc_type;
+ request.set_bw.bw = bandwidth;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.mrq = MRQ_SOCHUB_MBWT;
+ msg.tx.data = &request;
+ msg.tx.size = sizeof(request);
+
+ 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 < 0)
+ return msg.rx.ret;
+
+ return 0;
+}
+
+int tegra410_bpmp_mbwt_get(struct tegra_bpmp *bpmp,
+ unsigned int instance,
+ unsigned int vc_type,
+ unsigned int *bandwidth_out)
+{
+ struct mrq_sochub_mbwt_request request;
+ struct mrq_sochub_mbwt_response response;
+ struct tegra_bpmp_message msg;
+ int err;
+
+ if (!bandwidth_out)
+ return -EINVAL;
+
+ memset(&request, 0, sizeof(request));
+ request.cmd = CMD_SOCHUB_MBWT_GET_BW;
+ request.get_bw.instance = instance;
+ request.get_bw.vc_type = vc_type;
+
+ memset(&response, 0, sizeof(response));
+
+ memset(&msg, 0, sizeof(msg));
+ msg.mrq = MRQ_SOCHUB_MBWT;
+ msg.tx.data = &request;
+ msg.tx.size = sizeof(request);
+ msg.rx.data = &response;
+ msg.rx.size = sizeof(response);
+
+ 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 < 0)
+ return msg.rx.ret;
+
+ *bandwidth_out = response.get_bw.bw;
+
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH 5/6] firmware: tegra: bpmp: Add Tegra410 MBWT BPMP helpers
2026-06-15 8:37 ` [PATCH 5/6] firmware: tegra: bpmp: Add Tegra410 MBWT BPMP helpers Aniruddha Rao
@ 2026-07-10 10:19 ` Thierry Reding
0 siblings, 0 replies; 13+ messages in thread
From: Thierry Reding @ 2026-07-10 10:19 UTC (permalink / raw)
To: Aniruddha Rao; +Cc: thierry.reding, jonathanh, linux-tegra, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 6213 bytes --]
On Mon, Jun 15, 2026 at 08:37:30AM +0000, Aniruddha Rao wrote:
> Add helper functions that send MBWT requests to BPMP firmware on
> Tegra410.
>
> The helpers implement the GET_BW and SET_BW operations and provide a
> QUERY_ABI-based probe for command availability.
I'd rather not make this Tegra410 specific. We might need it on future
chips, too.
In fact, given the size, I'd be inclined to just include this
unconditionally in the BPMP driver.
> Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
> ---
> drivers/firmware/tegra/Makefile | 1 +
> drivers/firmware/tegra/bpmp-private.h | 31 +++++++
> drivers/firmware/tegra/bpmp-tegra410.c | 108 +++++++++++++++++++++++++
> 3 files changed, 140 insertions(+)
> create mode 100644 drivers/firmware/tegra/bpmp-tegra410.c
>
> diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
> index 41e2e4dc31d6..4310cc0ff294 100644
> --- a/drivers/firmware/tegra/Makefile
> +++ b/drivers/firmware/tegra/Makefile
> @@ -4,6 +4,7 @@ tegra-bpmp-$(CONFIG_ARCH_TEGRA_210_SOC) += bpmp-tegra210.o
> tegra-bpmp-$(CONFIG_ARCH_TEGRA_186_SOC) += bpmp-tegra186.o
> 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_410_SOC) += bpmp-tegra410.o
Just make this bpmp-mbwt.o or maybe even stick it into bpmp.c. It's
barely 100 lines of proper code.
> tegra-bpmp-$(CONFIG_ARCH_TEGRA_264_SOC) += bpmp-tegra186.o
> tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o
> obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o
> diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
> index ebde0e36ae20..c3f466ae5979 100644
> --- a/drivers/firmware/tegra/bpmp-private.h
> +++ b/drivers/firmware/tegra/bpmp-private.h
> @@ -35,4 +35,35 @@ struct tegra_bpmp_acpi_message {
> u8 data[TEGRA_BPMP_ACPI_BMRQ_DATA_SZ];
> };
>
> +#if IS_ENABLED(CONFIG_ARCH_TEGRA_410_SOC)
> +int tegra410_bpmp_mbwt_set(struct tegra_bpmp *bpmp, unsigned int instance,
> + unsigned int vc_type, unsigned int bandwidth);
> +int tegra410_bpmp_mbwt_get(struct tegra_bpmp *bpmp, unsigned int instance,
> + unsigned int vc_type, unsigned int *bandwidth_out);
> +bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
> + unsigned int cmd_code);
> +#else
> +static inline int tegra410_bpmp_mbwt_set(struct tegra_bpmp *bpmp,
> + unsigned int instance,
> + unsigned int vc_type,
> + unsigned int bandwidth)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static inline int tegra410_bpmp_mbwt_get(struct tegra_bpmp *bpmp,
> + unsigned int instance,
> + unsigned int vc_type,
> + unsigned int *bandwidth_out)
> +{
> + return -EOPNOTSUPP;
> +}
> +
> +static inline bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
> + unsigned int cmd_code)
> +{
> + return false;
> +}
> +#endif
We can then drop the 410 from the prefix and get rid of the dummy
implementations.
Thierry
> +
> #endif
> diff --git a/drivers/firmware/tegra/bpmp-tegra410.c b/drivers/firmware/tegra/bpmp-tegra410.c
> new file mode 100644
> index 000000000000..87c5dfab864b
> --- /dev/null
> +++ b/drivers/firmware/tegra/bpmp-tegra410.c
> @@ -0,0 +1,108 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/string.h>
> +
> +#include <soc/tegra/bpmp.h>
> +#include <soc/tegra/bpmp-abi.h>
> +
> +#include "bpmp-private.h"
> +
> +bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
> + unsigned int cmd_code)
> +{
> + struct mrq_sochub_mbwt_request request;
> + struct tegra_bpmp_message msg;
> + int err;
> +
> + memset(&request, 0, sizeof(request));
> + request.cmd = CMD_SOCHUB_MBWT_QUERY_ABI;
> + request.query_abi.cmd_code = cmd_code;
> +
> + memset(&msg, 0, sizeof(msg));
> + msg.mrq = MRQ_SOCHUB_MBWT;
> + msg.tx.data = &request;
> + msg.tx.size = sizeof(request);
> +
> + err = tegra_bpmp_transfer(bpmp, &msg);
> + if (err || msg.rx.ret)
> + return false;
> +
> + return true;
> +}
> +
> +int tegra410_bpmp_mbwt_set(struct tegra_bpmp *bpmp,
> + unsigned int instance,
> + unsigned int vc_type,
> + unsigned int bandwidth)
> +{
> + struct mrq_sochub_mbwt_request request;
> + struct tegra_bpmp_message msg;
> + int err;
> +
> + memset(&request, 0, sizeof(request));
> + request.cmd = CMD_SOCHUB_MBWT_SET_BW;
> + request.set_bw.instance = instance;
> + request.set_bw.vc_type = vc_type;
> + request.set_bw.bw = bandwidth;
> +
> + memset(&msg, 0, sizeof(msg));
> + msg.mrq = MRQ_SOCHUB_MBWT;
> + msg.tx.data = &request;
> + msg.tx.size = sizeof(request);
> +
> + 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 < 0)
> + return msg.rx.ret;
> +
> + return 0;
> +}
> +
> +int tegra410_bpmp_mbwt_get(struct tegra_bpmp *bpmp,
> + unsigned int instance,
> + unsigned int vc_type,
> + unsigned int *bandwidth_out)
> +{
> + struct mrq_sochub_mbwt_request request;
> + struct mrq_sochub_mbwt_response response;
> + struct tegra_bpmp_message msg;
> + int err;
> +
> + if (!bandwidth_out)
> + return -EINVAL;
> +
> + memset(&request, 0, sizeof(request));
> + request.cmd = CMD_SOCHUB_MBWT_GET_BW;
> + request.get_bw.instance = instance;
> + request.get_bw.vc_type = vc_type;
> +
> + memset(&response, 0, sizeof(response));
> +
> + memset(&msg, 0, sizeof(msg));
> + msg.mrq = MRQ_SOCHUB_MBWT;
> + msg.tx.data = &request;
> + msg.tx.size = sizeof(request);
> + msg.rx.data = &response;
> + msg.rx.size = sizeof(response);
> +
> + 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 < 0)
> + return msg.rx.ret;
> +
> + *bandwidth_out = response.get_bw.bw;
> +
> + return 0;
> +}
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 6/6] firmware: tegra: bpmp: Add Tegra410 MBWT sysfs interface
2026-06-15 8:37 [PATCH 0/6] firmware: tegra: bpmp: Add Tegra410 ACPI MBWT support Aniruddha Rao
` (4 preceding siblings ...)
2026-06-15 8:37 ` [PATCH 5/6] firmware: tegra: bpmp: Add Tegra410 MBWT BPMP helpers Aniruddha Rao
@ 2026-06-15 8:37 ` Aniruddha Rao
2026-07-10 10:46 ` Thierry Reding
5 siblings, 1 reply; 13+ messages in thread
From: Aniruddha Rao @ 2026-06-15 8:37 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
devices or a GPU connected over chip-to-chip link (Nv-Clink). That
information is not available to the kernel.
Tegra410 provides Memory Bandwidth Throttler (MBWT) controls for PCIe
and GPU connected over chip-to-chip link (Nv-Clink) traffic on the path
to DRAM. Each PCIe bandwidth group has a single shared cap for all
traffic in that group. A group may contain only PCIe devices, only a GPU
over Nv-Clink, or both PCIe and GPU traffic in a bifurcated topology.
Bandwidth for a group can be set per traffic type (PCIe Read, PCIe
Write, GPU over Nv-Clink).
Add a sysfs attribute on the tegra-bpmp platform device to expose a
narrow userspace interface/tuning knob for MBWT control.
A write to the attribute accepts a comma-separated tuple of
instance,vc_type,bandwidth. The instance field identifies the PCIe
bandwidth group. The vc_type field selects PCIe read, PCIe write, or GPU
over Nv-Clink traffic for that group, and bandwidth specifies the target
bandwidth limit to program. A read from the attribute queries firmware
for each documented instance and vc_type combination and returns one
tuple per line. Reads do not depend on a previous write. If firmware
returns an error for any GET_BW request, the read fails with that error.
For example, writing 0,1,100 programs a 100 GB/s cap for PCIe write
traffic in PCIe bandwidth group 0. A subsequent read reports the
firmware-returned tuples, including the bandwidth value reported by
firmware for PCIe write traffic in group 0.
Since Tegra410 is an ACPI-only platform, register the attribute only on
ACPI systems and 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>
---
.../ABI/testing/sys-platform-tegra-bpmp | 51 +++++
drivers/firmware/tegra/Makefile | 1 +
drivers/firmware/tegra/bpmp-private.h | 2 +
drivers/firmware/tegra/bpmp-tegra-sysfs.c | 210 ++++++++++++++++++
drivers/firmware/tegra/bpmp.c | 6 +
5 files changed, 270 insertions(+)
create mode 100644 Documentation/ABI/testing/sys-platform-tegra-bpmp
create mode 100644 drivers/firmware/tegra/bpmp-tegra-sysfs.c
diff --git a/Documentation/ABI/testing/sys-platform-tegra-bpmp b/Documentation/ABI/testing/sys-platform-tegra-bpmp
new file mode 100644
index 000000000000..2c08051ac39d
--- /dev/null
+++ b/Documentation/ABI/testing/sys-platform-tegra-bpmp
@@ -0,0 +1,51 @@
+What: /sys/bus/platform/devices/<bpmp-device>/bandwidth
+Date: June 2026
+KernelVersion: 7.1
+Contact: Aniruddha TVS Rao <anrao@nvidia.com>
+Description:
+ Provides access to the Tegra410 Memory Bandwidth Throttler
+ (MBWT) control exposed by BPMP firmware for PCIe and GPU
+ connected over chip-to-chip link (Nv-Clink) traffic on the
+ path to DRAM.
+
+ The attribute is present only on ACPI-based Tegra410 systems
+ and only when BPMP firmware reports support for the MBWT
+ GET_BW and SET_BW requests through its query ABI.
+
+ Each PCIe bandwidth group has a single shared cap for all
+ traffic in that group. A group may contain only PCIe devices,
+ only a GPU over Nv-Clink, or both PCIe and GPU traffic in a
+ bifurcated topology. Bandwidth for a group can be set per
+ traffic type (PCIe Read, PCIe Write, GPU over Nv-Clink).
+
+ A write accepts a comma-separated tuple of
+ "instance,vc_type,bandwidth". Spaces around comma-separated
+ fields are allowed.
+
+ instance identifies the PCIe bandwidth group. Valid values are
+ 0-5, where 0 = pcie0, 1 = pcie1, ..., 5 = pcie5.
+
+ vc_type selects the traffic type for the selected group:
+ 0 = PCIe read
+ 1 = PCIe write
+ 2 = GPU over Nv-Clink
+
+ bandwidth specifies the target bandwidth cap in GB/s. Values
+ outside 1-110 (inclusive) are rejected by the driver before
+ issuing MBWT_SET. Firmware may still reject values within
+ that range.
+
+ A read queries firmware for each documented instance and
+ vc_type combination, and returns one tuple per line. Reads
+ do not depend on a previous write. If firmware returns an
+ error for any GET_BW request, the read fails with that error.
+
+ Example:
+ echo 0,1,100 > .../bandwidth
+ cat .../bandwidth
+ 0,0,100
+ 0,1,100
+ ...
+
+Users: Platform integration and bandwidth tuning on ACPI-based
+ Tegra410 systems (PCIe and GPU over Nv-Clink caps).
diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
index 4310cc0ff294..2fd060c66523 100644
--- a/drivers/firmware/tegra/Makefile
+++ b/drivers/firmware/tegra/Makefile
@@ -5,6 +5,7 @@ tegra-bpmp-$(CONFIG_ARCH_TEGRA_186_SOC) += bpmp-tegra186.o
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_410_SOC) += bpmp-tegra410.o
+tegra-bpmp-y += bpmp-tegra-sysfs.o
tegra-bpmp-$(CONFIG_ARCH_TEGRA_264_SOC) += bpmp-tegra186.o
tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o
obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o
diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
index c3f466ae5979..ae26cc203aa4 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -66,4 +66,6 @@ static inline bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
}
#endif
+int tegra_bpmp_sysfs_register(struct tegra_bpmp *bpmp);
+
#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..150c189ba8da
--- /dev/null
+++ b/drivers/firmware/tegra/bpmp-tegra-sysfs.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/kstrtox.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+
+#include <soc/tegra/bpmp.h>
+#include <soc/tegra/bpmp-abi.h>
+
+#include "bpmp-private.h"
+
+/* Documented sysfs / ABI bounds; firmware may still reject a request. */
+#define TEGRA_BPMP_MBWT_INSTANCE_MAX 5U
+#define TEGRA_BPMP_MBWT_VC_MAX 2U
+#define TEGRA_BPMP_MBWT_BW_MIN 1U
+#define TEGRA_BPMP_MBWT_BW_MAX 110U
+
+struct tegra_bpmp_mbwt_sysfs {
+ struct device_attribute dev_attr;
+ struct tegra_bpmp *bpmp;
+ /* Serializes bandwidth I/O. */
+ struct mutex lock;
+};
+
+static struct tegra_bpmp_mbwt_sysfs *
+tegra_bpmp_mbwt_sysfs_from_attr(struct device_attribute *attr)
+{
+ return container_of(attr, struct tegra_bpmp_mbwt_sysfs, dev_attr);
+}
+
+static int tegra_bpmp_mbwt_valid_tuple(unsigned int instance,
+ unsigned int vc_type,
+ unsigned int bandwidth)
+{
+ if (instance > TEGRA_BPMP_MBWT_INSTANCE_MAX)
+ return -EINVAL;
+ if (vc_type > TEGRA_BPMP_MBWT_VC_MAX)
+ return -EINVAL;
+ if (bandwidth < TEGRA_BPMP_MBWT_BW_MIN ||
+ bandwidth > TEGRA_BPMP_MBWT_BW_MAX)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int tegra_bpmp_mbwt_parse(const char *buf, size_t count,
+ unsigned int *instance,
+ unsigned int *vc_type,
+ unsigned int *bandwidth)
+{
+ unsigned int values[3];
+ char *copy, *cur, *tok;
+ unsigned int i = 0;
+ int err = 0;
+
+ copy = kmemdup_nul(buf, count, GFP_KERNEL);
+ if (!copy)
+ return -ENOMEM;
+
+ cur = strim(copy);
+ while ((tok = strsep(&cur, ",")) != NULL) {
+ if (i >= ARRAY_SIZE(values)) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ tok = strim(tok);
+ if (!*tok) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ err = kstrtou32(tok, 0, &values[i]);
+ if (err)
+ goto out;
+
+ i++;
+ }
+
+ if (i != ARRAY_SIZE(values)) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ *instance = values[0];
+ *vc_type = values[1];
+ *bandwidth = values[2];
+ err = 0;
+
+out:
+ kfree(copy);
+ return err;
+}
+
+static ssize_t bandwidth_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tegra_bpmp_mbwt_sysfs *mbwt;
+ unsigned int instance, vc_type, bandwidth;
+ ssize_t len = 0;
+ int err;
+
+ mbwt = tegra_bpmp_mbwt_sysfs_from_attr(attr);
+
+ mutex_lock(&mbwt->lock);
+ for (instance = 0; instance <= TEGRA_BPMP_MBWT_INSTANCE_MAX; instance++) {
+ for (vc_type = 0; vc_type <= TEGRA_BPMP_MBWT_VC_MAX; vc_type++) {
+ err = tegra410_bpmp_mbwt_get(mbwt->bpmp, instance,
+ vc_type, &bandwidth);
+ if (err) {
+ mutex_unlock(&mbwt->lock);
+ return err;
+ }
+
+ len += sysfs_emit_at(buf, len, "%u,%u,%u\n", instance,
+ vc_type, bandwidth);
+ }
+ }
+ mutex_unlock(&mbwt->lock);
+
+ return len;
+}
+
+static ssize_t bandwidth_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct tegra_bpmp_mbwt_sysfs *mbwt;
+ unsigned int instance, vc_type, bandwidth;
+ int err;
+
+ err = tegra_bpmp_mbwt_parse(buf, count, &instance, &vc_type,
+ &bandwidth);
+ if (err)
+ return err;
+
+ err = tegra_bpmp_mbwt_valid_tuple(instance, vc_type, bandwidth);
+ if (err)
+ return err;
+
+ mbwt = tegra_bpmp_mbwt_sysfs_from_attr(attr);
+
+ mutex_lock(&mbwt->lock);
+ err = tegra410_bpmp_mbwt_set(mbwt->bpmp, instance, 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;
+
+ device_remove_file(mbwt->bpmp->dev, &mbwt->dev_attr);
+}
+
+int tegra_bpmp_sysfs_register(struct tegra_bpmp *bpmp)
+{
+ struct tegra_bpmp_mbwt_sysfs *mbwt;
+ int err;
+
+ if (!ACPI_HANDLE(bpmp->dev))
+ return 0;
+
+ if (!tegra_bpmp_mrq_is_supported(bpmp, MRQ_SOCHUB_MBWT))
+ return 0;
+
+ /*
+ * MRQ_QUERY_ABI only confirms that the MBWT MRQ is implemented. The
+ * firmware reports GET_BW / SET_BW support through the MBWT ABI query.
+ */
+ if (!tegra410_bpmp_mbwt_cmd_is_supported(bpmp, CMD_SOCHUB_MBWT_GET_BW) ||
+ !tegra410_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);
+
+ sysfs_attr_init(&mbwt->dev_attr.attr);
+ mbwt->dev_attr.attr.name = "bandwidth";
+ mbwt->dev_attr.attr.mode = 0644;
+ mbwt->dev_attr.show = bandwidth_show;
+ mbwt->dev_attr.store = bandwidth_store;
+
+ err = device_create_file(bpmp->dev, &mbwt->dev_attr);
+ if (err)
+ return err;
+
+ err = devm_add_action(bpmp->dev, tegra_bpmp_mbwt_sysfs_teardown, mbwt);
+ if (err) {
+ device_remove_file(bpmp->dev, &mbwt->dev_attr);
+ return err;
+ }
+
+ return 0;
+}
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index e9c0d6d3e24d..f637bdaa57cd 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -982,6 +982,12 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
if (err < 0)
goto free_mrq;
+ err = tegra_bpmp_sysfs_register(bpmp);
+ if (err < 0)
+ dev_err(&pdev->dev,
+ "Failed registering sysfs attribute to the BPMP platform device: %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] 13+ messages in thread* Re: [PATCH 6/6] firmware: tegra: bpmp: Add Tegra410 MBWT sysfs interface
2026-06-15 8:37 ` [PATCH 6/6] firmware: tegra: bpmp: Add Tegra410 MBWT sysfs interface Aniruddha Rao
@ 2026-07-10 10:46 ` Thierry Reding
0 siblings, 0 replies; 13+ messages in thread
From: Thierry Reding @ 2026-07-10 10:46 UTC (permalink / raw)
To: Aniruddha Rao; +Cc: thierry.reding, jonathanh, linux-tegra, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 14810 bytes --]
On Mon, Jun 15, 2026 at 08:37:31AM +0000, 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
> devices or a GPU connected over chip-to-chip link (Nv-Clink). That
> information is not available to the kernel.
>
> Tegra410 provides Memory Bandwidth Throttler (MBWT) controls for PCIe
> and GPU connected over chip-to-chip link (Nv-Clink) traffic on the path
> to DRAM. Each PCIe bandwidth group has a single shared cap for all
> traffic in that group. A group may contain only PCIe devices, only a GPU
> over Nv-Clink, or both PCIe and GPU traffic in a bifurcated topology.
> Bandwidth for a group can be set per traffic type (PCIe Read, PCIe
> Write, GPU over Nv-Clink).
>
> Add a sysfs attribute on the tegra-bpmp platform device to expose a
> narrow userspace interface/tuning knob for MBWT control.
>
> A write to the attribute accepts a comma-separated tuple of
> instance,vc_type,bandwidth. The instance field identifies the PCIe
> bandwidth group. The vc_type field selects PCIe read, PCIe write, or GPU
> over Nv-Clink traffic for that group, and bandwidth specifies the target
> bandwidth limit to program. A read from the attribute queries firmware
> for each documented instance and vc_type combination and returns one
> tuple per line. Reads do not depend on a previous write. If firmware
> returns an error for any GET_BW request, the read fails with that error.
>
> For example, writing 0,1,100 programs a 100 GB/s cap for PCIe write
> traffic in PCIe bandwidth group 0. A subsequent read reports the
> firmware-returned tuples, including the bandwidth value reported by
> firmware for PCIe write traffic in group 0.
>
> Since Tegra410 is an ACPI-only platform, register the attribute only on
> ACPI systems and only when BPMP firmware reports support for the MBWT
> GET_BW and SET_BW requests through its query ABI.
I think we should make this purely feature based and not tie this to
ACPI. We might end up with needing this on non-ACPI systems at some
point, and even if we don't it's generally clearer to bind functionality
to features, especially if they can be detected at runtime like in this
case.
>
> Signed-off-by: Aniruddha Rao <anrao@nvidia.com>
> ---
> .../ABI/testing/sys-platform-tegra-bpmp | 51 +++++
> drivers/firmware/tegra/Makefile | 1 +
> drivers/firmware/tegra/bpmp-private.h | 2 +
> drivers/firmware/tegra/bpmp-tegra-sysfs.c | 210 ++++++++++++++++++
> drivers/firmware/tegra/bpmp.c | 6 +
> 5 files changed, 270 insertions(+)
> create mode 100644 Documentation/ABI/testing/sys-platform-tegra-bpmp
> create mode 100644 drivers/firmware/tegra/bpmp-tegra-sysfs.c
>
> diff --git a/Documentation/ABI/testing/sys-platform-tegra-bpmp b/Documentation/ABI/testing/sys-platform-tegra-bpmp
> new file mode 100644
> index 000000000000..2c08051ac39d
> --- /dev/null
> +++ b/Documentation/ABI/testing/sys-platform-tegra-bpmp
> @@ -0,0 +1,51 @@
> +What: /sys/bus/platform/devices/<bpmp-device>/bandwidth
> +Date: June 2026
> +KernelVersion: 7.1
> +Contact: Aniruddha TVS Rao <anrao@nvidia.com>
> +Description:
> + Provides access to the Tegra410 Memory Bandwidth Throttler
> + (MBWT) control exposed by BPMP firmware for PCIe and GPU
> + connected over chip-to-chip link (Nv-Clink) traffic on the
> + path to DRAM.
> +
> + The attribute is present only on ACPI-based Tegra410 systems
> + and only when BPMP firmware reports support for the MBWT
> + GET_BW and SET_BW requests through its query ABI.
> +
> + Each PCIe bandwidth group has a single shared cap for all
> + traffic in that group. A group may contain only PCIe devices,
> + only a GPU over Nv-Clink, or both PCIe and GPU traffic in a
> + bifurcated topology. Bandwidth for a group can be set per
> + traffic type (PCIe Read, PCIe Write, GPU over Nv-Clink).
> +
> + A write accepts a comma-separated tuple of
> + "instance,vc_type,bandwidth". Spaces around comma-separated
> + fields are allowed.
> +
> + instance identifies the PCIe bandwidth group. Valid values are
> + 0-5, where 0 = pcie0, 1 = pcie1, ..., 5 = pcie5.
> +
> + vc_type selects the traffic type for the selected group:
> + 0 = PCIe read
> + 1 = PCIe write
> + 2 = GPU over Nv-Clink
> +
> + bandwidth specifies the target bandwidth cap in GB/s. Values
> + outside 1-110 (inclusive) are rejected by the driver before
> + issuing MBWT_SET. Firmware may still reject values within
> + that range.
If firmware is already filtering, I don't think we should filter in
software at all. That's just going to lead to a situation where we need
to keep both in sync manually.
> +
> + A read queries firmware for each documented instance and
> + vc_type combination, and returns one tuple per line. Reads
> + do not depend on a previous write. If firmware returns an
> + error for any GET_BW request, the read fails with that error.
> +
> + Example:
> + echo 0,1,100 > .../bandwidth
> + cat .../bandwidth
> + 0,0,100
> + 0,1,100
> + ...
> +
> +Users: Platform integration and bandwidth tuning on ACPI-based
> + Tegra410 systems (PCIe and GPU over Nv-Clink caps).
I recall that this is evolved from a prior sysfs interface and was
changed to this based on earlier input (including from me). I'm having
second thoughts about this particular iteration, though. The sysfs
documentation is pretty clear that attributes should be simple values
that don't need a lot of parsing. The comma-separated list here doesn't
adhere to that.
This particular format also has the downside of requiring a lot of
implicit knowledge.
I think we could address both issues with a hierachy that might look
somewhat like this:
<bpmp>
|-- mbwt/
| |-- pcie0/
| | |-- read
| | |-- write
| | |-- gpu
| |-- pcie1/
| | |-- read
| | |-- ...
| ...
| |-- pcie5/
| | |-- ...
Note that this is different from the first iteration in that it allows
requests to be atomic. It also has the benefit of making the number of
groups and virtual channels discoverable (i.e. the driver knows them
based on SoC generation and userspace can probe by simply walking the
sysfs hierarchy).
The value written (or read) is then simply the bandwidth in GB/s.
> diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile
> index 4310cc0ff294..2fd060c66523 100644
> --- a/drivers/firmware/tegra/Makefile
> +++ b/drivers/firmware/tegra/Makefile
> @@ -5,6 +5,7 @@ tegra-bpmp-$(CONFIG_ARCH_TEGRA_186_SOC) += bpmp-tegra186.o
> 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_410_SOC) += bpmp-tegra410.o
> +tegra-bpmp-y += bpmp-tegra-sysfs.o
I think this needs to be conditional on CONFIG_SYSFS.
> tegra-bpmp-$(CONFIG_ARCH_TEGRA_264_SOC) += bpmp-tegra186.o
> tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o
> obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o
> diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h
> index c3f466ae5979..ae26cc203aa4 100644
> --- a/drivers/firmware/tegra/bpmp-private.h
> +++ b/drivers/firmware/tegra/bpmp-private.h
> @@ -66,4 +66,6 @@ static inline bool tegra410_bpmp_mbwt_cmd_is_supported(struct tegra_bpmp *bpmp,
> }
> #endif
>
> +int tegra_bpmp_sysfs_register(struct tegra_bpmp *bpmp);
> +
> #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..150c189ba8da
> --- /dev/null
> +++ b/drivers/firmware/tegra/bpmp-tegra-sysfs.c
> @@ -0,0 +1,210 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026, NVIDIA CORPORATION.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/kstrtox.h>
> +#include <linux/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/sysfs.h>
> +
> +#include <soc/tegra/bpmp.h>
> +#include <soc/tegra/bpmp-abi.h>
> +
> +#include "bpmp-private.h"
> +
> +/* Documented sysfs / ABI bounds; firmware may still reject a request. */
> +#define TEGRA_BPMP_MBWT_INSTANCE_MAX 5U
> +#define TEGRA_BPMP_MBWT_VC_MAX 2U
> +#define TEGRA_BPMP_MBWT_BW_MIN 1U
> +#define TEGRA_BPMP_MBWT_BW_MAX 110U
> +
> +struct tegra_bpmp_mbwt_sysfs {
> + struct device_attribute dev_attr;
> + struct tegra_bpmp *bpmp;
> + /* Serializes bandwidth I/O. */
> + struct mutex lock;
> +};
> +
> +static struct tegra_bpmp_mbwt_sysfs *
> +tegra_bpmp_mbwt_sysfs_from_attr(struct device_attribute *attr)
> +{
> + return container_of(attr, struct tegra_bpmp_mbwt_sysfs, dev_attr);
> +}
> +
> +static int tegra_bpmp_mbwt_valid_tuple(unsigned int instance,
> + unsigned int vc_type,
> + unsigned int bandwidth)
> +{
> + if (instance > TEGRA_BPMP_MBWT_INSTANCE_MAX)
> + return -EINVAL;
> + if (vc_type > TEGRA_BPMP_MBWT_VC_MAX)
> + return -EINVAL;
> + if (bandwidth < TEGRA_BPMP_MBWT_BW_MIN ||
> + bandwidth > TEGRA_BPMP_MBWT_BW_MAX)
> + return -EINVAL;
These checks (except maybe the bandwidth minimum and maximum) become
completely irrelevant with the new sysfs interface because there's
simply no way to pass invalid values.
> +
> + return 0;
> +}
> +
> +static int tegra_bpmp_mbwt_parse(const char *buf, size_t count,
> + unsigned int *instance,
> + unsigned int *vc_type,
> + unsigned int *bandwidth)
> +{
> + unsigned int values[3];
> + char *copy, *cur, *tok;
> + unsigned int i = 0;
> + int err = 0;
> +
> + copy = kmemdup_nul(buf, count, GFP_KERNEL);
> + if (!copy)
> + return -ENOMEM;
> +
> + cur = strim(copy);
> + while ((tok = strsep(&cur, ",")) != NULL) {
> + if (i >= ARRAY_SIZE(values)) {
> + err = -EINVAL;
> + goto out;
> + }
> +
> + tok = strim(tok);
> + if (!*tok) {
> + err = -EINVAL;
> + goto out;
> + }
> +
> + err = kstrtou32(tok, 0, &values[i]);
> + if (err)
> + goto out;
> +
> + i++;
> + }
> +
> + if (i != ARRAY_SIZE(values)) {
> + err = -EINVAL;
> + goto out;
> + }
> +
> + *instance = values[0];
> + *vc_type = values[1];
> + *bandwidth = values[2];
> + err = 0;
> +
> +out:
> + kfree(copy);
> + return err;
> +}
> +
> +static ssize_t bandwidth_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct tegra_bpmp_mbwt_sysfs *mbwt;
> + unsigned int instance, vc_type, bandwidth;
> + ssize_t len = 0;
> + int err;
> +
> + mbwt = tegra_bpmp_mbwt_sysfs_from_attr(attr);
> +
> + mutex_lock(&mbwt->lock);
> + for (instance = 0; instance <= TEGRA_BPMP_MBWT_INSTANCE_MAX; instance++) {
> + for (vc_type = 0; vc_type <= TEGRA_BPMP_MBWT_VC_MAX; vc_type++) {
> + err = tegra410_bpmp_mbwt_get(mbwt->bpmp, instance,
> + vc_type, &bandwidth);
> + if (err) {
> + mutex_unlock(&mbwt->lock);
> + return err;
> + }
> +
> + len += sysfs_emit_at(buf, len, "%u,%u,%u\n", instance,
> + vc_type, bandwidth);
> + }
> + }
I suppose there's some benefit in this type of overview to get a quick
look at what's been programmed. We can still add a "summary" type of
file to the top-level that shows this, though I'd go with a slightly
different format, maybe something that mirrors the tree hierarchy of the
attributes.
> + mutex_unlock(&mbwt->lock);
> +
> + return len;
> +}
> +
> +static ssize_t bandwidth_store(struct device *dev,
> + struct device_attribute *attr, const char *buf,
> + size_t count)
> +{
> + struct tegra_bpmp_mbwt_sysfs *mbwt;
> + unsigned int instance, vc_type, bandwidth;
> + int err;
> +
> + err = tegra_bpmp_mbwt_parse(buf, count, &instance, &vc_type,
> + &bandwidth);
> + if (err)
> + return err;
> +
> + err = tegra_bpmp_mbwt_valid_tuple(instance, vc_type, bandwidth);
> + if (err)
> + return err;
> +
> + mbwt = tegra_bpmp_mbwt_sysfs_from_attr(attr);
> +
> + mutex_lock(&mbwt->lock);
> + err = tegra410_bpmp_mbwt_set(mbwt->bpmp, instance, 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;
> +
> + device_remove_file(mbwt->bpmp->dev, &mbwt->dev_attr);
> +}
> +
> +int tegra_bpmp_sysfs_register(struct tegra_bpmp *bpmp)
> +{
> + struct tegra_bpmp_mbwt_sysfs *mbwt;
> + int err;
> +
> + if (!ACPI_HANDLE(bpmp->dev))
> + return 0;
> +
> + if (!tegra_bpmp_mrq_is_supported(bpmp, MRQ_SOCHUB_MBWT))
> + return 0;
> +
> + /*
> + * MRQ_QUERY_ABI only confirms that the MBWT MRQ is implemented. The
> + * firmware reports GET_BW / SET_BW support through the MBWT ABI query.
> + */
> + if (!tegra410_bpmp_mbwt_cmd_is_supported(bpmp, CMD_SOCHUB_MBWT_GET_BW) ||
> + !tegra410_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);
> +
> + sysfs_attr_init(&mbwt->dev_attr.attr);
> + mbwt->dev_attr.attr.name = "bandwidth";
> + mbwt->dev_attr.attr.mode = 0644;
> + mbwt->dev_attr.show = bandwidth_show;
> + mbwt->dev_attr.store = bandwidth_store;
> +
> + err = device_create_file(bpmp->dev, &mbwt->dev_attr);
> + if (err)
> + return err;
> +
> + err = devm_add_action(bpmp->dev, tegra_bpmp_mbwt_sysfs_teardown, mbwt);
> + if (err) {
> + device_remove_file(bpmp->dev, &mbwt->dev_attr);
> + return err;
> + }
> +
> + return 0;
> +}
> diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
> index e9c0d6d3e24d..f637bdaa57cd 100644
> --- a/drivers/firmware/tegra/bpmp.c
> +++ b/drivers/firmware/tegra/bpmp.c
> @@ -982,6 +982,12 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
> if (err < 0)
> goto free_mrq;
>
> + err = tegra_bpmp_sysfs_register(bpmp);
Maybe stick with the convention set by debugfs and call this
tegra_bpmp_init_sysfs().
> + if (err < 0)
> + dev_err(&pdev->dev,
> + "Failed registering sysfs attribute to the BPMP platform device: %d\n",
And similarly for this error message.
Thierry
> + err);
> +
> err = tegra_bpmp_init_debugfs(bpmp);
> if (err < 0)
> dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err);
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread