From 8a4662e7423f2b1c4250d773c6e25a4b136418b7 Mon Sep 17 00:00:00 2001 From: Koba Ko Date: Fri, 8 May 2026 02:17:55 +0800 Subject: [PATCH 1/3] NVIDIA: VR: SAUCE: tegra: bpmp: Move channel, resource init to helper 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 (backported from V4 internal mail <20260423140823.2848045-2-anrao@nvidia.com>) Signed-off-by: Koba Ko --- 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 e74bba7ccc443..927e9b78ea0fa 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -699,19 +699,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); @@ -721,37 +711,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) @@ -771,28 +809,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); From fd9097f38b08fe890012e3e97120935dc174047f Mon Sep 17 00:00:00 2001 From: Koba Ko Date: Fri, 8 May 2026 02:18:09 +0800 Subject: [PATCH 2/3] NVIDIA: VR: SAUCE: tegra: bpmp: Add ACPI support This patch adds required changes in the Tegra BPMP driver to make it compatible with ACPI based platforms. On ACPI systems, IPC is handled through the AML method instead of the core kernel framework using Mailboxes and IVC. Bypass clock, reset and powergate init calls as these are not controlled by the Linux drivers on ACPI based systems. Signed-off-by: Aniruddha Rao (backported from V4 internal mail <20260423140823.2848045-3-anrao@nvidia.com>) [kobak: Add !ACPI_HANDLE(bpmp->dev) guard around bpmp->soc->ops->init because ACPI match driver_data=0 makes bpmp->soc NULL; make BPMP debugfs directory per-device on ACPI systems to avoid duplicate /sys/kernel/debug/bpmp collision on dual NVDA3001 instances.] Signed-off-by: Koba Ko --- drivers/firmware/tegra/bpmp-debugfs.c | 19 +++- drivers/firmware/tegra/bpmp-private.h | 8 ++ drivers/firmware/tegra/bpmp.c | 146 +++++++++++++++++++++++--- 3 files changed, 154 insertions(+), 19 deletions(-) diff --git a/drivers/firmware/tegra/bpmp-debugfs.c b/drivers/firmware/tegra/bpmp-debugfs.c index 4221fed70ad48..9898aa9396c56 100644 --- a/drivers/firmware/tegra/bpmp-debugfs.c +++ b/drivers/firmware/tegra/bpmp-debugfs.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. */ +#include #include #include #include @@ -771,6 +772,8 @@ static int bpmp_populate_debugfs_shmem(struct tegra_bpmp *bpmp) int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) { + const char *root_name = "bpmp"; + char *acpi_root_name = NULL; struct dentry *root; bool inband; int err; @@ -780,13 +783,23 @@ int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) if (!inband && !tegra_bpmp_mrq_is_supported(bpmp, MRQ_DEBUGFS)) return 0; - root = debugfs_create_dir("bpmp", NULL); + if (ACPI_HANDLE(bpmp->dev)) { + acpi_root_name = kasprintf(GFP_KERNEL, "bpmp-%s", + dev_name(bpmp->dev)); + if (!acpi_root_name) + return -ENOMEM; + + root_name = acpi_root_name; + } + + root = debugfs_create_dir(root_name, NULL); + kfree(acpi_root_name); if (IS_ERR(root)) - return -ENOMEM; + return PTR_ERR(root); bpmp->debugfs_mirror = debugfs_create_dir("debug", root); if (IS_ERR(bpmp->debugfs_mirror)) { - err = -ENOMEM; + err = PTR_ERR(bpmp->debugfs_mirror); goto out; } diff --git a/drivers/firmware/tegra/bpmp-private.h b/drivers/firmware/tegra/bpmp-private.h index 07c3d46abb874..6cb5b8b544561 100644 --- a/drivers/firmware/tegra/bpmp-private.h +++ b/drivers/firmware/tegra/bpmp-private.h @@ -26,4 +26,12 @@ struct tegra_bpmp_ops { extern const struct tegra_bpmp_ops tegra186_bpmp_ops; extern const struct tegra_bpmp_ops tegra210_bpmp_ops; +#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 927e9b78ea0fa..701a2ec5eed12 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 #include #include #include @@ -309,12 +310,93 @@ static ssize_t tegra_bpmp_channel_write(struct tegra_bpmp_channel *channel, static int __maybe_unused tegra_bpmp_resume(struct device *dev); +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; + u32 i; + size_t rbuf_len, rdata_len; + + 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); + 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; + } + + 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(output.pointer); + return -EINVAL; + } + + msg->rx.ret = (int)pkg.status; + if (msg->rx.data && msg->rx.size) + memcpy(msg->rx.data, pkg.data, msg->rx.size); + + /* Free memory allocated by ACPI core */ + kfree(output.pointer); + return 0; +} + 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; @@ -355,8 +437,8 @@ 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; @@ -394,6 +476,15 @@ 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 (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, @@ -472,6 +563,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; @@ -497,6 +591,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); @@ -572,11 +669,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, @@ -614,10 +717,13 @@ static int tegra_bpmp_get_firmware_tag_old(struct tegra_bpmp *bpmp, char *tag, msg.tx.data = &request; msg.tx.size = sizeof(request); - local_irq_save(flags); - err = tegra_bpmp_transfer_atomic(bpmp, &msg); - local_irq_restore(flags); - + if (ACPI_HANDLE(bpmp->dev)) { + err = tegra_bpmp_transfer_acpi(bpmp, &msg); + } else { + local_irq_save(flags); + err = tegra_bpmp_transfer_atomic(bpmp, &msg); + local_irq_restore(flags); + } if (err == 0) memcpy(tag, virt, TAG_SZ); @@ -703,6 +809,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); @@ -743,6 +852,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; @@ -784,16 +896,18 @@ static int tegra_bpmp_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bpmp); - if (bpmp->soc->ops->init) { + if (!ACPI_HANDLE(bpmp->dev) && 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) - 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) { From 758f258afbfa39891b95de08226702d7bcb89391 Mon Sep 17 00:00:00 2001 From: Koba Ko Date: Fri, 8 May 2026 02:18:32 +0800 Subject: [PATCH 3/3] NVIDIA: VR: SAUCE: tegra: bpmp: Add sysfs for memory bandwidth QoS Tegra410 exposes memory bandwidth QoS for PCIe and GPU UPHY traffic on the path to DRAM. Each bandwidth group can cap PCIe read, PCIe write, or combined GPU UPHY read and write traffic, with target limits. The memory bandwidth QoS is not exposed as ordinary host MMIO and cannot be controlled from the kernel. The bandwidth limits can be programmed by sending the corresponding requests (MBWT MRQ) to the BPMP. On Tegra410, an ACPI-based platform, Linux BPMP driver does not use the device-tree mailbox path for communicating with the BPMP firmware. As a result, there is no existing client driver or interface that can be used to send the memory bandwidth requests to the BPMP. This patch exposes a sysfs directory mbwt_control on the tegra-bpmp platform device with pcie_instance_id, vc_type, and bandwidth. Writing bandwidth issues an MBWT_SET for the selected group (pcie_instance_id) and traffic class (vc_type). A read issues MBWT_GET and returns pcie_instance_id, vc_type, and the bandwidth value reported by firmware. These attributes are exposed only if MBWT QUERY probe reports both MBWT_SET and MBWT_GET commands as supported. ABI documented in Documentation/ABI/testing/sys-platform-tegra-bpmp Signed-off-by: Aniruddha Rao (backported from V4 internal mail <20260423140823.2848045-4-anrao@nvidia.com>) [kobak: Keep functional MRQ_SOCHUB_MBWT ABI definitions and sysfs interface from V4; condense verbose per-field ABI comments while preserving enum/struct layout and Documentation/ABI coverage.] Signed-off-by: Koba Ko --- .../ABI/testing/sys-platform-tegra-bpmp | 51 +++ drivers/firmware/tegra/Makefile | 1 + drivers/firmware/tegra/bpmp-tegra-sysfs.c | 324 ++++++++++++++++++ drivers/firmware/tegra/bpmp.c | 12 + include/soc/tegra/bpmp-abi.h | 64 +++- include/soc/tegra/bpmp.h | 7 + 6 files changed, 458 insertions(+), 1 deletion(-) 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 0000000000000..0be09cd8d00f3 --- /dev/null +++ b/Documentation/ABI/testing/sys-platform-tegra-bpmp @@ -0,0 +1,51 @@ +What: /sys/bus/platform/devices//mbwt_control/pcie_instance_id +What: /sys/bus/platform/devices//mbwt_control/vc_type +What: /sys/bus/platform/devices//mbwt_control/bandwidth +Date: March 2026 +KernelVersion: 6.18 +Contact: Aniruddha TVS Rao +Description: + On ACPI-based Tegra systems the BPMP driver does not use the + device-tree mailbox path; firmware interaction is via AML. This sysfs + interface is a userspace tuning knob for memory bandwidth throttler + (MBWT) settings. + + On Tegra410, the PCIe bandwidth control path exposes QoS that caps + aggregate bandwidth for PCIe and for GPU traffic over UPHY. 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 on UPHY, or PCIe and + GPU together in a bifurcated topology. + + Following attributes appear under a kobject named mbwt_control on the + tegra-bpmp platform device (the same struct device as the driver + binds to), only when that device has an ACPI companion and BPMP + firmware reports support for both MBWT_GET and MBWT_SET via the MBWT + QUERY probe. + + pcie_instance_id (RW): + PCIe bandwidth group index: 0 = pcie0, 1 = pcie1, ..., 5 = pcie5. + + vc_type (RW): + Traffic type to cap for that group: + 0 = PCIe read + 1 = PCIe write + 2 = GPU UPHY read plus write (combined) + + bandwidth (RW): + Target bandwidth cap in GB/s for the pcie_instance_id and vc_type + currently stored in the other two attributes. Intended range is + 1-110 GB/s; firmware validates the request (via MRQ MBWT_SET). + + When read, issues a MBWT_GET for that same pcie_instance_id and vc_type + and returns one line of three decimal fields separated by ASCII + space: PCIe instance ID, vc_type, and the bandwidth value in GB/s + returned by firmware. + + Example: + echo 0 > .../mbwt_control/pcie_instance_id + echo 1 > .../mbwt_control/vc_type + echo 100 > .../mbwt_control/bandwidth + cat .../mbwt_control/bandwidth + +Users: Customer tuning of PCIe and GPU UPHY bandwidth caps on + ACPI-based Tegra410 systems. diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index 41e2e4dc31d63..6c577e59b3ee7 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -7,4 +7,5 @@ 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 obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o +obj-$(CONFIG_TEGRA_BPMP) += bpmp-tegra-sysfs.o obj-$(CONFIG_TEGRA_IVC) += ivc.o diff --git a/drivers/firmware/tegra/bpmp-tegra-sysfs.c b/drivers/firmware/tegra/bpmp-tegra-sysfs.c new file mode 100644 index 0000000000000..4e5203d04ad31 --- /dev/null +++ b/drivers/firmware/tegra/bpmp-tegra-sysfs.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, NVIDIA CORPORATION. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bpmp-private.h" + +struct tegra_bpmp_mbwt_sysfs { + struct kobject kobj; + /* Serializes pcie_instance_id and vc_type stores and bandwidth I/O. */ + struct mutex lock; + struct tegra_bpmp *bpmp; + unsigned int pcie_instance_id; + unsigned int vc_type; +}; + +#define to_mbwt_sysfs(k) container_of((k), struct tegra_bpmp_mbwt_sysfs, kobj) + +static void tegra_bpmp_mbwt_kobj_release(struct kobject *kobj) +{ + /* Lifetime is managed by devm; no dynamic allocation here. */ +} + +static const struct kobj_type tegra_bpmp_mbwt_ktype = { + .release = tegra_bpmp_mbwt_kobj_release, + .sysfs_ops = &kobj_sysfs_ops, +}; + +/** + * tegra_sochub_mbwt_query_abi() - Ask BPMP whether an MBWT sub-command is supported. + * @bpmp: BPMP handle + * @cmd_code: Sub-command to probe (e.g. CMD_SOCHUB_MBWT_SET_BW) + * + * Returns 0 if the firmware reports the sub-command is supported (MRQ error 0). + * Returns a negative errno if the transfer fails, or %-EOPNOTSUPP if the + * firmware reports the sub-command is not supported. + */ +static int tegra_sochub_mbwt_query_abi(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) + return err; + + if (msg.rx.ret) + return -EOPNOTSUPP; + + return 0; +} + +static int tegra_sochub_set_mbwt(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, + "Failed setting up the SocHub MBWT with error %d\n", + err); + return err; +} + +static int tegra_sochub_get_mbwt(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 cmd_sochub_mbwt_get_bw_resp mbwt; + struct tegra_bpmp_message msg; + int err; + + 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, + "Failed reading the SocHub MBWT with error %d\n", + err); + return err; + } + if (msg.rx.ret < 0) + return -EINVAL; + + memcpy(&mbwt, &response.get_bw, sizeof(response.get_bw)); + if (bandwidth_out) + *bandwidth_out = mbwt.bw; + + return 0; +} + +static ssize_t pcie_instance_id_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt = to_mbwt_sysfs(kobj); + unsigned int id; + + mutex_lock(&mbwt->lock); + id = mbwt->pcie_instance_id; + mutex_unlock(&mbwt->lock); + + return sysfs_emit(buf, "%u\n", id); +} + +static ssize_t pcie_instance_id_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt = to_mbwt_sysfs(kobj); + unsigned int val; + int err; + + err = kstrtou32(buf, 0, &val); + if (err) + return err; + + mutex_lock(&mbwt->lock); + mbwt->pcie_instance_id = val; + mutex_unlock(&mbwt->lock); + + return count; +} + +static ssize_t vc_type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt = to_mbwt_sysfs(kobj); + unsigned int vt; + + mutex_lock(&mbwt->lock); + vt = mbwt->vc_type; + mutex_unlock(&mbwt->lock); + + return sysfs_emit(buf, "%u\n", vt); +} + +static ssize_t vc_type_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt = to_mbwt_sysfs(kobj); + unsigned int val; + int err; + + err = kstrtou32(buf, 0, &val); + if (err) + return err; + + mutex_lock(&mbwt->lock); + mbwt->vc_type = val; + mutex_unlock(&mbwt->lock); + + return count; +} + +static ssize_t bandwidth_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt = to_mbwt_sysfs(kobj); + unsigned int inst, vt, bw; + int err; + + mutex_lock(&mbwt->lock); + inst = mbwt->pcie_instance_id; + vt = mbwt->vc_type; + mutex_unlock(&mbwt->lock); + + err = tegra_sochub_get_mbwt(mbwt->bpmp, inst, vt, &bw); + if (err) + return err; + + return sysfs_emit(buf, "%u %u %u\n", inst, vt, bw); +} + +static ssize_t bandwidth_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt = to_mbwt_sysfs(kobj); + unsigned int bw; + unsigned int inst, vt; + int err; + + err = kstrtou32(buf, 0, &bw); + if (err) + return err; + + mutex_lock(&mbwt->lock); + inst = mbwt->pcie_instance_id; + vt = mbwt->vc_type; + mutex_unlock(&mbwt->lock); + + err = tegra_sochub_set_mbwt(mbwt->bpmp, inst, vt, bw); + if (err) + return err; + + return count; +} + +static struct kobj_attribute pcie_instance_id_attr = + __ATTR(pcie_instance_id, 0644, pcie_instance_id_show, pcie_instance_id_store); +static struct kobj_attribute vc_type_attr = + __ATTR(vc_type, 0644, vc_type_show, vc_type_store); +static struct kobj_attribute bandwidth_attr = + __ATTR(bandwidth, 0644, bandwidth_show, bandwidth_store); + +static struct attribute *mbwt_attrs[] = { + &pcie_instance_id_attr.attr, + &vc_type_attr.attr, + &bandwidth_attr.attr, + NULL, +}; + +static const struct attribute_group mbwt_attr_group = { + .attrs = mbwt_attrs, +}; + +static void tegra_bpmp_mbwt_sysfs_teardown(void *data) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt = data; + + sysfs_remove_group(&mbwt->kobj, &mbwt_attr_group); + kobject_del(&mbwt->kobj); + kobject_put(&mbwt->kobj); +} + +int tegra_bpmp_sysfs_register(struct tegra_bpmp *bpmp) +{ + struct tegra_bpmp_mbwt_sysfs *mbwt; + int err; + + if (!ACPI_HANDLE(bpmp->dev)) + return 0; + + err = tegra_sochub_mbwt_query_abi(bpmp, CMD_SOCHUB_MBWT_SET_BW); + if (err) + return 0; + + err = tegra_sochub_mbwt_query_abi(bpmp, CMD_SOCHUB_MBWT_GET_BW); + if (err) + return 0; + + mbwt = devm_kzalloc(bpmp->dev, sizeof(*mbwt), GFP_KERNEL); + if (!mbwt) + return -ENOMEM; + + mbwt->bpmp = bpmp; + mutex_init(&mbwt->lock); + + kobject_init(&mbwt->kobj, &tegra_bpmp_mbwt_ktype); + err = kobject_add(&mbwt->kobj, &bpmp->dev->kobj, "mbwt_control"); + if (err) { + kobject_put(&mbwt->kobj); + return err; + } + + err = sysfs_create_group(&mbwt->kobj, &mbwt_attr_group); + if (err) + goto err_put; + + err = devm_add_action(bpmp->dev, tegra_bpmp_mbwt_sysfs_teardown, mbwt); + if (err) { + sysfs_remove_group(&mbwt->kobj, &mbwt_attr_group); + goto err_put; + } + + return 0; + +err_put: + kobject_del(&mbwt->kobj); + kobject_put(&mbwt->kobj); + return err; +} diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 701a2ec5eed12..eb07a45db4849 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -927,6 +927,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); @@ -1029,10 +1035,16 @@ static const struct of_device_id tegra_bpmp_match[] = { { } }; +static const struct acpi_device_id tegra_bpmp_acpi_match[] = { + {.id = "NVDA3001", .driver_data = 0}, + { } +}; + 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, }, diff --git a/include/soc/tegra/bpmp-abi.h b/include/soc/tegra/bpmp-abi.h index dc0789c20333a..0b8d51556f124 100644 --- a/include/soc/tegra/bpmp-abi.h +++ b/include/soc/tegra/bpmp-abi.h @@ -341,6 +341,7 @@ struct mrq_response { #define MRQ_GEARS 82U #define MRQ_BWMGR_INT 83U #define MRQ_OC_STATUS 84U +#define MRQ_SOCHUB_MBWT 96U /** @cond DEPRECATED */ #define MRQ_RESERVED_2 2U @@ -374,7 +375,7 @@ struct mrq_response { * @brief Maximum MRQ code to be sent by CPU software to * BPMP. Subject to change in future */ -#define MAX_CPU_MRQ_ID 84U +#define MAX_CPU_MRQ_ID 96U /** * @addtogroup MRQ_Payloads @@ -3911,6 +3912,67 @@ struct mrq_gears_response { /** @} Gears */ /** @endcond bpmp_th500 */ +/** @cond (bpmp_tb500) + * @ingroup MRQ_Codes + * @def MRQ_SOCHUB_MBWT + * @brief Configure per-virtual-channel bandwidth caps for a SoC Hub instance 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 { + CMD_SOCHUB_MBWT_QUERY_ABI = 0, + CMD_SOCHUB_MBWT_GET_BW = 1, + CMD_SOCHUB_MBWT_SET_BW = 2, +}; + +struct cmd_sochub_mbwt_query_abi_req { + uint32_t cmd_code; +} BPMP_ABI_PACKED; + +struct cmd_sochub_mbwt_get_bw_req { + uint32_t instance; + uint32_t vc_type; +} BPMP_ABI_PACKED; + +struct cmd_sochub_mbwt_set_bw_req { + uint32_t instance; + uint32_t vc_type; + uint32_t bw; +} BPMP_ABI_PACKED; + +struct cmd_sochub_mbwt_get_bw_resp { + uint32_t bw; +} BPMP_ABI_PACKED; + +struct mrq_sochub_mbwt_request { + 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; + +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 diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index f5e4ac5b8cce8..bae8ad985cbcb 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -140,6 +140,7 @@ int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, void tegra_bpmp_free_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, void *data); bool tegra_bpmp_mrq_is_supported(struct tegra_bpmp *bpmp, unsigned int mrq); +int tegra_bpmp_sysfs_register(struct tegra_bpmp *bpmp); #else static inline struct tegra_bpmp *tegra_bpmp_get(struct device *dev) { @@ -181,6 +182,12 @@ static inline bool tegra_bpmp_mrq_is_supported(struct tegra_bpmp *bpmp, { return false; } + +static inline int tegra_bpmp_sysfs_register(struct tegra_bpmp *bpmp) +{ + return -EOPNOTSUPP; +} + #endif void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp);