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 v5 5/5] firmware: tegra: bpmp: Add MBWT sysfs interface
Date: Tue, 29 Sep 2026 09:59:15 +0000 [thread overview]
Message-ID: <20260929095915.2215685-6-anrao@nvidia.com> (raw)
In-Reply-To: <20260929095915.2215685-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.
Describe the available bandwidth groups and traffic types in platform
data. Each entry maps a sysfs name to the corresponding firmware
instance and VC type so that these values can vary between platforms.
Add sysfs attributes on the tegra-bpmp platform device to expose a
narrow userspace interface for MBWT control. The attributes are arranged
as mbwt/<bandwidth-group>/<traffic-type>/bandwidth. The directory names
come from platform data. The current platform description uses pcie0
through pcie5 for bandwidth groups and pcie_read, pcie_write and
nvclink for traffic types.
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 v4:
- Describe MBWT bandwidth groups and traffic types with platform data so
firmware instance and VC type values can vary between platforms.
- Document the sysfs hierarchy using platform-defined bandwidth-group and
traffic-type names.
Changes since v3:
- Update the sysfs ABI Documentation date to September 2026 and target
release to 7.4.
Changes since v2:
- Rename bpmp-tegra-sysfs.c to bpmp-sysfs.c.
Changes since v1:
- Register the interface only when firmware supports GET_BW and SET_BW.
- Expose per-traffic bandwidth attributes.
- Remove software bandwidth range checks.
- Guard sysfs registration with CONFIG_SYSFS.
- Rename the sysfs initialization helper.
- Use generic MBWT wording.
.../ABI/testing/sysfs-platform-tegra-bpmp | 48 ++++
drivers/firmware/tegra/Makefile | 1 +
drivers/firmware/tegra/bpmp-private.h | 26 +++
drivers/firmware/tegra/bpmp-sysfs.c | 207 ++++++++++++++++++
drivers/firmware/tegra/bpmp.c | 35 +++
include/soc/tegra/bpmp.h | 2 +
6 files changed, 319 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-platform-tegra-bpmp
create mode 100644 drivers/firmware/tegra/bpmp-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..5d0b525bd72a
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-tegra-bpmp
@@ -0,0 +1,48 @@
+What: /sys/bus/platform/devices/<bpmp-device>/mbwt/<bandwidth-group>/<traffic-type>/bandwidth
+Date: September 2026
+KernelVersion: 7.4
+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 <bandwidth-group> directory identifies one PCIe bandwidth
+ group. The available groups and their directory names are
+ platform-specific. Current platforms use pcie0 through pcie5.
+ 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 <traffic-type> directory selects the traffic type for the
+ selected group. The following traffic types may be available:
+
+ 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..97f7a5db9d0a 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-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 f0a7d6254bdc..39cf31771836 100644
--- a/drivers/firmware/tegra/bpmp-private.h
+++ b/drivers/firmware/tegra/bpmp-private.h
@@ -24,6 +24,23 @@ struct tegra_bpmp_ops {
int (*resume)(struct tegra_bpmp *bpmp);
};
+struct tegra_bpmp_mbwt_vc {
+ const char *name;
+ unsigned int type;
+};
+
+struct tegra_bpmp_mbwt_group {
+ const char *name;
+ unsigned int id;
+ const struct tegra_bpmp_mbwt_vc *vcs;
+ unsigned int num_vcs;
+};
+
+struct tegra_bpmp_mbwt_soc {
+ const struct tegra_bpmp_mbwt_group *groups;
+ unsigned int num_groups;
+};
+
extern const struct tegra_bpmp_ops tegra186_bpmp_ops;
extern const struct tegra_bpmp_ops tegra210_bpmp_ops;
@@ -39,4 +56,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-sysfs.c b/drivers/firmware/tegra/bpmp-sysfs.c
new file mode 100644
index 000000000000..c7bf7f13f5d7
--- /dev/null
+++ b/drivers/firmware/tegra/bpmp-sysfs.c
@@ -0,0 +1,207 @@
+// 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"
+
+struct tegra_bpmp_mbwt_attr {
+ struct kobj_attribute attr;
+ struct tegra_bpmp_mbwt_sysfs *mbwt;
+ struct kobject *kobj;
+ unsigned int instance;
+ unsigned int vc_type;
+};
+
+struct tegra_bpmp_mbwt_sysfs {
+ struct tegra_bpmp *bpmp;
+ struct kobject *root;
+ struct kobject **groups;
+ struct tegra_bpmp_mbwt_attr *attrs;
+ unsigned int num_groups;
+ unsigned int num_attrs;
+ /* Serializes bandwidth requests to firmware. */
+ struct mutex lock;
+};
+
+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;
+
+ for (i = 0; i < mbwt->num_attrs; i++) {
+ sysfs_remove_file(mbwt->attrs[i].kobj,
+ &mbwt->attrs[i].attr.attr);
+ kobject_put(mbwt->attrs[i].kobj);
+ }
+
+ for (i = 0; i < mbwt->num_groups; i++)
+ kobject_put(mbwt->groups[i]);
+
+ kobject_put(mbwt->root);
+}
+
+static int tegra_bpmp_mbwt_sysfs_add_group(struct tegra_bpmp_mbwt_sysfs *mbwt,
+ const struct tegra_bpmp_mbwt_group *group)
+{
+ struct tegra_bpmp_mbwt_attr *attr;
+ struct kobject *group_kobj;
+ unsigned int i;
+ int err;
+
+ group_kobj = kobject_create_and_add(group->name, mbwt->root);
+ if (!group_kobj)
+ return -ENOMEM;
+
+ mbwt->groups[mbwt->num_groups++] = group_kobj;
+
+ for (i = 0; i < group->num_vcs; i++) {
+ attr = &mbwt->attrs[mbwt->num_attrs];
+ attr->kobj = kobject_create_and_add(group->vcs[i].name,
+ group_kobj);
+ if (!attr->kobj)
+ 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 = group->id;
+ attr->vc_type = group->vcs[i].type;
+
+ err = sysfs_create_file(attr->kobj, &attr->attr.attr);
+ if (err) {
+ kobject_put(attr->kobj);
+ return err;
+ }
+
+ mbwt->num_attrs++;
+ }
+
+ return 0;
+}
+
+int tegra_bpmp_init_sysfs(struct tegra_bpmp *bpmp)
+{
+ const struct tegra_bpmp_mbwt_soc *soc = bpmp->soc->mbwt;
+ struct tegra_bpmp_mbwt_sysfs *mbwt;
+ unsigned int i, num_attrs = 0;
+ int err;
+
+ if (!soc)
+ return 0;
+
+ 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->groups = devm_kcalloc(bpmp->dev, soc->num_groups,
+ sizeof(*mbwt->groups), GFP_KERNEL);
+ if (!mbwt->groups)
+ return -ENOMEM;
+
+ for (i = 0; i < soc->num_groups; i++)
+ num_attrs += soc->groups[i].num_vcs;
+
+ mbwt->attrs = devm_kcalloc(bpmp->dev, num_attrs,
+ sizeof(*mbwt->attrs), GFP_KERNEL);
+ if (!mbwt->attrs)
+ return -ENOMEM;
+
+ mbwt->root = kobject_create_and_add("mbwt", &bpmp->dev->kobj);
+ if (!mbwt->root)
+ return -ENOMEM;
+
+ for (i = 0; i < soc->num_groups; i++) {
+ err = tegra_bpmp_mbwt_sysfs_add_group(mbwt, &soc->groups[i]);
+ 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 f2b739977f13..d91e833120b1 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -1085,6 +1085,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev)
goto deinit;
}
+ 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);
@@ -1186,6 +1190,36 @@ static const struct of_device_id tegra_bpmp_match[] = {
};
#ifdef CONFIG_ACPI
+#define TEGRA_BPMP_MBWT_VC_PCIE_READ 0
+#define TEGRA_BPMP_MBWT_VC_PCIE_WRITE 1
+#define TEGRA_BPMP_MBWT_VC_NVCLINK 2
+
+static const struct tegra_bpmp_mbwt_vc tegra_bpmp_acpi_mbwt_vcs[] = {
+ { .name = "pcie_read", .type = TEGRA_BPMP_MBWT_VC_PCIE_READ },
+ { .name = "pcie_write", .type = TEGRA_BPMP_MBWT_VC_PCIE_WRITE },
+ { .name = "nvclink", .type = TEGRA_BPMP_MBWT_VC_NVCLINK },
+};
+
+static const struct tegra_bpmp_mbwt_group tegra_bpmp_acpi_mbwt_groups[] = {
+ { .name = "pcie0", .id = 0, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+ .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+ { .name = "pcie1", .id = 1, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+ .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+ { .name = "pcie2", .id = 2, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+ .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+ { .name = "pcie3", .id = 3, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+ .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+ { .name = "pcie4", .id = 4, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+ .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+ { .name = "pcie5", .id = 5, .vcs = tegra_bpmp_acpi_mbwt_vcs,
+ .num_vcs = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_vcs) },
+};
+
+static const struct tegra_bpmp_mbwt_soc tegra_bpmp_acpi_mbwt_soc = {
+ .groups = tegra_bpmp_acpi_mbwt_groups,
+ .num_groups = ARRAY_SIZE(tegra_bpmp_acpi_mbwt_groups),
+};
+
static int tegra_bpmp_acpi_init(struct tegra_bpmp *bpmp)
{
return tegra_bpmp_ping(bpmp);
@@ -1197,6 +1231,7 @@ 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,
+ .mbwt = &tegra_bpmp_acpi_mbwt_soc,
};
static const struct acpi_device_id tegra_bpmp_acpi_match[] = {
diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h
index a33582590a3b..f55db918ccb1 100644
--- a/include/soc/tegra/bpmp.h
+++ b/include/soc/tegra/bpmp.h
@@ -16,6 +16,7 @@
#include <soc/tegra/bpmp-abi.h>
struct tegra_bpmp_clk;
+struct tegra_bpmp_mbwt_soc;
struct tegra_bpmp_ops;
struct tegra_bpmp_soc {
@@ -28,6 +29,7 @@ struct tegra_bpmp_soc {
} channels;
const struct tegra_bpmp_ops *ops;
+ const struct tegra_bpmp_mbwt_soc *mbwt;
unsigned int num_resets;
};
--
2.43.0
prev parent reply other threads:[~2026-09-29 10:00 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-29 9:59 [PATCH v5 0/5] firmware: tegra: bpmp: Add ACPI and MBWT support Aniruddha Rao
2026-09-29 9:59 ` [PATCH v5 1/5] firmware: tegra: bpmp: Move channel initialization to helper Aniruddha Rao
2026-09-29 9:59 ` [PATCH v5 2/5] firmware: tegra: bpmp: Add ACPI support Aniruddha Rao
2026-09-29 9:59 ` [PATCH v5 3/5] firmware: tegra: bpmp: Add the Memory Bandwidth Throttler ABI definitions Aniruddha Rao
2026-09-29 9:59 ` [PATCH v5 4/5] firmware: tegra: bpmp: Add MBWT BPMP helpers Aniruddha Rao
2026-09-29 9:59 ` Aniruddha Rao [this message]
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=20260929095915.2215685-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
all inboxes | Powered by JetHome®