diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index 620cf3fdd607..86173ae6b448 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -1,9 +1,11 @@ # SPDX-License-Identifier: GPL-2.0-only +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. tegra-bpmp-y = bpmp.o 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_TEGRA_HV_DRIVER) += bpmp-tegra194-hv.o tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.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 182bfe396516..de18d8d11484 100644 --- a/drivers/firmware/tegra/bpmp-private.h +++ b/drivers/firmware/tegra/bpmp-private.h @@ -1,7 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 2018, NVIDIA CORPORATION. - */ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-FileCopyrightText: Copyright (c) 2018-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #ifndef __FIRMWARE_TEGRA_BPMP_PRIVATE_H #define __FIRMWARE_TEGRA_BPMP_PRIVATE_H @@ -31,5 +29,7 @@ extern const struct tegra_bpmp_ops tegra186_bpmp_ops; #if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) extern const struct tegra_bpmp_ops tegra210_bpmp_ops; #endif - +#if IS_ENABLED(CONFIG_TEGRA_HV_DRIVER) +extern const struct tegra_bpmp_ops tegra194_bpmp_hv_ops; +#endif #endif diff --git a/drivers/firmware/tegra/bpmp-tegra194-hv.c b/drivers/firmware/tegra/bpmp-tegra194-hv.c new file mode 100644 index 000000000000..d171aa532e2e --- /dev/null +++ b/drivers/firmware/tegra/bpmp-tegra194-hv.c @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ + +#include +#include +#include + +#include +#include +#include +#include "bpmp-private.h" + +/* utilizing the struct tegra_ivc *ivc in struct tegra_bpmp_channel + * to store struct tegra_hv_ivc_cookie pointer, being used by this driver + * to use hypervisor exposed ivc queue implementation, so that we do not + * need to introduced new member in struct tegra_bpmp_channel which + * will be unused in upstream kernel + */ +#define to_hv_ivc(ivc) ((struct tegra_hv_ivc_cookie *)ivc) + +static struct tegra_hv_ivc_cookie **hv_bpmp_ivc_cookies; +static struct device_node *hv_of_node; + +static irqreturn_t tegra194_hv_bpmp_rx_handler(int irq, void *bpmp) +{ + tegra_bpmp_handle_rx(bpmp); + return IRQ_HANDLED; +} + +static int tegra194_hv_bpmp_channel_init(struct tegra_bpmp_channel *channel, + struct tegra_bpmp *bpmp, + unsigned int queue_id, bool threaded) +{ + static int cookie_idx; + struct tegra_hv_ivc_cookie *hv_ivc; + int err; + + hv_ivc = tegra_hv_ivc_reserve(hv_of_node, queue_id, NULL); + channel->ivc = (void *)hv_ivc; + + if (IS_ERR_OR_NULL(hv_ivc)) { + pr_err("%s: Failed to reserve ivc queue @index %d\n", + __func__, queue_id); + goto request_cleanup; + } + + if (hv_ivc->frame_size < MSG_DATA_MIN_SZ) { + pr_err("%s: Frame size is too small\n", __func__); + goto request_cleanup; + } + + hv_bpmp_ivc_cookies[cookie_idx++] = hv_ivc; + + /* init completion */ + init_completion(&channel->completion); + channel->bpmp = bpmp; + + if (threaded) { + err = request_threaded_irq( + hv_ivc->irq, + tegra194_hv_bpmp_rx_handler, NULL, + IRQF_NO_SUSPEND, + "bpmp_irq_handler", bpmp); + } else { + err = 0; + } + + if (err) { + pr_err("%s: Failed to request irq %d for index %d\n", + __func__, hv_ivc->irq, queue_id); + goto request_cleanup; + } + + return 0; + +request_cleanup: + return -1; +} + +static bool tegra194_bpmp_hv_is_message_ready(struct tegra_bpmp_channel *channel) +{ + struct tegra_hv_ivc_cookie *hv_ivc = to_hv_ivc(channel->ivc); + void *frame; + + frame = tegra_hv_ivc_read_get_next_frame(hv_ivc); + if (IS_ERR(frame)) { + iosys_map_clear(&channel->ib); + return false; + } + iosys_map_set_vaddr(&channel->ib, frame); + + return true; +} + +static int tegra194_bpmp_hv_ack_message(struct tegra_bpmp_channel *channel) +{ + struct tegra_hv_ivc_cookie *hv_ivc = to_hv_ivc(channel->ivc); + + return tegra_hv_ivc_read_advance(hv_ivc); +} + +static bool tegra194_hv_bpmp_is_channel_free(struct tegra_bpmp_channel *channel) +{ + struct tegra_hv_ivc_cookie *hv_ivc = to_hv_ivc(channel->ivc); + void *frame; + + frame = tegra_hv_ivc_write_get_next_frame(hv_ivc); + if (IS_ERR(frame)) { + iosys_map_clear(&channel->ob); + return false; + } + iosys_map_set_vaddr(&channel->ob, frame); + + return true; +} + +static int tegra194_hv_bpmp_post_message(struct tegra_bpmp_channel *channel) +{ + struct tegra_hv_ivc_cookie *hv_ivc = to_hv_ivc(channel->ivc); + + return tegra_hv_ivc_write_advance(hv_ivc); +} + +static void tegra194_hv_bpmp_channel_reset(struct tegra_bpmp_channel *channel) +{ + struct tegra_hv_ivc_cookie *hv_ivc = to_hv_ivc(channel->ivc); + + /* reset the channel state */ + tegra_hv_ivc_channel_reset(hv_ivc); + + /* sync the channel state with BPMP */ + while (tegra_hv_ivc_channel_notified(hv_ivc)); +} + +static int tegra194_hv_bpmp_resume(struct tegra_bpmp *bpmp) +{ + unsigned int i; + + /* reset message channels */ + tegra194_hv_bpmp_channel_reset(bpmp->tx_channel); + + for (i = 0; i < bpmp->threaded.count; i++) + tegra194_hv_bpmp_channel_reset(&bpmp->threaded_channels[i]); + + return 0; +} + +static int tegra194_hv_ivc_notify(struct tegra_bpmp *bpmp) +{ + struct tegra_hv_ivc_cookie *hv_ivc = to_hv_ivc(bpmp->tx_channel->ivc); + + tegra_hv_ivc_notify(hv_ivc); + return 0; +} + +static int tegra194_hv_bpmp_init(struct tegra_bpmp *bpmp) +{ + struct device_node *of_node = bpmp->dev->of_node; + int err, index; + uint32_t first_ivc_queue, num_ivc_queues; + + /* get starting ivc queue id & ivc queue count */ + hv_of_node = of_parse_phandle(of_node, "ivc_queue", 0); + if (!hv_of_node) { + pr_err("%s: Unable to find hypervisor node\n", __func__); + return -EINVAL; + } + of_node_put(hv_of_node); + + err = of_property_read_u32_index(of_node, "ivc_queue", 1, + &first_ivc_queue); + if (err) { + pr_err("%s: Failed to read start IVC queue\n", + __func__); + return err; + } + + err = of_property_read_u32_index(of_node, "ivc_queue", 2, + &num_ivc_queues); + if (err) { + pr_err("%s: Failed to read range of IVC queues\n", + __func__); + return err; + } + + /* verify total queue count meets expectations */ + if (num_ivc_queues < (bpmp->soc->channels.thread.count + + bpmp->soc->channels.cpu_tx.count + bpmp->soc->channels.cpu_rx.count)) { + pr_err("%s: no of ivc queues in DT < required no of channels\n", __func__); + return -EINVAL; + } + + hv_bpmp_ivc_cookies = kzalloc(sizeof(struct tegra_hv_ivc_cookie *) * + num_ivc_queues, GFP_KERNEL); + + if (!hv_bpmp_ivc_cookies) { + pr_err("%s: Failed to allocate memory\n", __func__); + return -ENOMEM; + } + + err = tegra194_hv_bpmp_channel_init(bpmp->tx_channel, bpmp, + bpmp->soc->channels.cpu_tx.offset + first_ivc_queue, false); + if (err < 0) { + pr_err("%s: Failed initialize tx channel\n", __func__); + goto cleanup; + } + + for (index = 0; index < bpmp->threaded.count; index++) { + unsigned int idx = bpmp->soc->channels.thread.offset + index; + + err = tegra194_hv_bpmp_channel_init(&bpmp->threaded_channels[index], + bpmp, idx + first_ivc_queue, true); + if (err < 0) { + pr_err("%s: Failed initialize tx channel\n", __func__); + goto cleanup; + } + } + + tegra194_hv_bpmp_resume(bpmp); + + return 0; + +cleanup: + for (index = 0; index < num_ivc_queues; index++) { + if (hv_bpmp_ivc_cookies[index]) { + tegra_hv_ivc_unreserve(hv_bpmp_ivc_cookies[index]); + hv_bpmp_ivc_cookies[index] = NULL; + } + } + kfree(hv_bpmp_ivc_cookies); + + return err; +} + +static void tegra194_hv_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel) +{ + struct tegra_hv_ivc_cookie *hv_ivc = to_hv_ivc(channel->ivc); + + free_irq(hv_ivc->irq, &hv_ivc); + tegra_hv_ivc_unreserve(hv_ivc); + kfree(hv_ivc); +} + +static void tegra194_hv_bpmp_deinit(struct tegra_bpmp *bpmp) +{ + unsigned int i; + + tegra194_hv_bpmp_channel_cleanup(bpmp->tx_channel); + + for (i = 0; i < bpmp->threaded.count; i++) + tegra194_hv_bpmp_channel_cleanup(&bpmp->threaded_channels[i]); +} + +const struct tegra_bpmp_ops tegra194_bpmp_hv_ops = { + .init = tegra194_hv_bpmp_init, + .deinit = tegra194_hv_bpmp_deinit, + .is_response_ready = tegra194_bpmp_hv_is_message_ready, + .is_request_ready = tegra194_bpmp_hv_is_message_ready, + .ack_response = tegra194_bpmp_hv_ack_message, + .ack_request = tegra194_bpmp_hv_ack_message, + .is_response_channel_free = tegra194_hv_bpmp_is_channel_free, + .is_request_channel_free = tegra194_hv_bpmp_is_channel_free, + .post_response = tegra194_hv_bpmp_post_message, + .post_request = tegra194_hv_bpmp_post_message, + .ring_doorbell = tegra194_hv_ivc_notify, + .resume = tegra194_hv_bpmp_resume, +}; diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index c149fe19dba6..4326674242bb 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -1,10 +1,9 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. - */ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* SPDX-FileCopyrightText: Copyright (c) 2016-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include #include +#include #include #include #include @@ -557,7 +556,9 @@ static int tegra_bpmp_ping(struct tegra_bpmp *bpmp) struct mrq_ping_response response; struct mrq_ping_request request; struct tegra_bpmp_message msg; +#if (!IS_ENABLED(CONFIG_PREEMPT_RT)) unsigned long flags; +#endif ktime_t start, end; int err; @@ -573,11 +574,17 @@ static int tegra_bpmp_ping(struct tegra_bpmp *bpmp) msg.rx.data = &response; msg.rx.size = sizeof(response); +#if (!IS_ENABLED(CONFIG_PREEMPT_RT)) local_irq_save(flags); start = ktime_get(); err = tegra_bpmp_transfer_atomic(bpmp, &msg); end = ktime_get(); local_irq_restore(flags); +#else + start = ktime_get(); + err = tegra_bpmp_transfer(bpmp, &msg); + end = ktime_get(); +#endif if (!err) dev_dbg(bpmp->dev, @@ -678,7 +685,10 @@ void tegra_bpmp_handle_rx(struct tegra_bpmp *bpmp) count = bpmp->soc->channels.thread.count; busy = bpmp->threaded.busy; - if (tegra_bpmp_is_request_ready(channel)) { + /* Check if supported incoming channel. + * This won't be called for Guest Linux as channel->ib mapping won't be set. + */ + if (iosys_map_is_set(&channel->ib) && tegra_bpmp_is_request_ready(channel)) { unsigned int mrq = tegra_bpmp_mb_read_field(&channel->ib, code); tegra_bpmp_handle_mrq(bpmp, mrq, channel); @@ -884,6 +894,25 @@ static const struct tegra_bpmp_soc tegra210_soc = { }; #endif +#if IS_ENABLED(CONFIG_TEGRA_HV_DRIVER) +static const struct tegra_bpmp_soc t194_safe_hv_soc = { + .channels = { + .cpu_tx = { + .offset = 3, + .count = 1, + .timeout = 30 * USEC_PER_SEC, + }, + .thread = { + .offset = 0, + .count = 3, + .timeout = 30 * USEC_PER_SEC, + }, + }, + .ops = &tegra194_bpmp_hv_ops, + .num_resets = 193, +}; +#endif + static const struct of_device_id tegra_bpmp_match[] = { #if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) || \ IS_ENABLED(CONFIG_ARCH_TEGRA_194_SOC) || \ @@ -892,6 +921,9 @@ static const struct of_device_id tegra_bpmp_match[] = { #endif #if IS_ENABLED(CONFIG_ARCH_TEGRA_210_SOC) { .compatible = "nvidia,tegra210-bpmp", .data = &tegra210_soc }, +#endif +#if IS_ENABLED(CONFIG_TEGRA_HV_DRIVER) + { .compatible = "nvidia,tegra194-safe-bpmp-hv", .data = &t194_safe_hv_soc }, #endif { } };