From: Aniruddha Rao <anrao@nvidia.com>
To: <thierry.reding@kernel.org>, <jonathanh@nvidia.com>
Cc: <linux-tegra@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
"Aniruddha Rao" <anrao@nvidia.com>
Subject: [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface
Date: Wed, 22 Jul 2026 11:05:44 +0000 [thread overview]
Message-ID: <20260722110544.193551-6-anrao@nvidia.com> (raw)
In-Reply-To: <20260722110544.193551-1-anrao@nvidia.com>
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
next prev parent reply other threads:[~2026-07-22 11:06 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Aniruddha Rao [this message]
2026-07-23 5:21 ` [PATCH v2 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface Mikko Perttunen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260722110544.193551-6-anrao@nvidia.com \
--to=anrao@nvidia.com \
--cc=jonathanh@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-tegra@vger.kernel.org \
--cc=thierry.reding@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox