Files
linux-nv-oot/drivers/virt/tegra/tegra_hv.c
Manish Bhardwaj bc0fa73ed6 nvidia-oot: expose tegra_hv_ivc_tx_empty API for hv comm driver
Jira HYP-20929

Change-Id: I4ba92c8c7e76cc391d1c4a8a2e8d22f312cde7fa
Signed-off-by: Manish Bhardwaj <mbhardwaj@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3369454
Tested-by: Daniel Kudrow <dkudrow@nvidia.com>
GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Daniel Kudrow <dkudrow@nvidia.com>
Reviewed-by: Suresh Venkatachalam <skathirampat@nvidia.com>
2025-07-24 10:20:35 +00:00

1100 lines
26 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
/*
* SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#include <nvidia/conftest.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <soc/tegra/virt/syscalls.h>
#include <soc/tegra/virt/hv-ivc.h>
#include <soc/tegra/ivc_ext.h>
#include "tegra_hv.h"
#define ERR(...) pr_err("tegra_hv: " __VA_ARGS__)
#define INFO(...) pr_info("tegra_hv: " __VA_ARGS__)
#define DRV_NAME "tegra_hv"
struct tegra_hv_data;
static struct property interrupts_prop = {
.name = "interrupts",
};
struct hv_ivc {
struct tegra_hv_data *hvd;
/*
* ivc_devs are stored in an id-indexed array; this field indicates
* a valid array entry.
*/
int valid;
/* channel configuration */
struct tegra_ivc ivc;
const struct tegra_hv_queue_data *qd;
const struct ivc_shared_area *area;
const struct guest_ivc_info *givci;
int other_guestid;
const struct tegra_hv_ivc_ops *cookie_ops;
struct tegra_hv_ivc_cookie cookie;
/* This lock synchronizes the reserved flag. */
struct mutex lock;
int reserved;
char name[16];
int irq;
};
#define cookie_to_ivc_dev(_cookie) \
container_of(_cookie, struct hv_ivc, cookie)
/* Describe all info needed to do IVC to one particular guest */
struct guest_ivc_info {
uintptr_t shmem; /* IO remapped shmem */
size_t length; /* length of shmem */
};
struct hv_mempool {
struct tegra_hv_ivm_cookie ivmk;
const struct ivc_mempool *mpd;
struct mutex lock;
int reserved;
};
struct tegra_hv_data {
const struct ivc_info_page *info;
int guestid;
struct guest_ivc_info *guest_ivc_info;
/* ivc_devs is indexed by queue id */
struct hv_ivc *ivc_devs;
uint32_t max_qid;
/* array with length info->nr_mempools */
struct hv_mempool *mempools;
struct class *hv_class;
struct device_node *dev;
};
/*
* Global HV state for read-only access by tegra_hv_... APIs
*
* This should be accessed only through get_hvd().
*/
static struct tegra_hv_data *tegra_hv_data;
struct ivc_notify_info {
// Trap based notification
uintptr_t trap_region_base_va;
uintptr_t trap_region_base_ipa;
uintptr_t trap_region_end_ipa;
uint64_t trap_region_size;
// MSI based notification
uintptr_t msi_region_base_va;
uintptr_t msi_region_base_ipa;
uintptr_t msi_region_end_ipa;
uint64_t msi_region_size;
};
static struct ivc_notify_info ivc_notify;
bool is_tegra_hypervisor_mode(void)
{
#ifdef CONFIG_OF
return of_property_read_bool(of_chosen,
"nvidia,tegra-hypervisor-mode");
#else
return false;
#endif
}
EXPORT_SYMBOL(is_tegra_hypervisor_mode);
static void ivc_raise_irq(struct tegra_ivc *ivc_channel, void *data)
{
struct hv_ivc *ivc = container_of(ivc_channel, struct hv_ivc, ivc);
if (WARN_ON(!ivc->cookie.notify_va))
return;
*ivc->cookie.notify_va = ivc->qd->raise_irq;
}
static struct tegra_hv_data *get_hvd(void)
{
if (!tegra_hv_data) {
INFO("%s: not initialized yet\n", __func__);
return ERR_PTR(-EPROBE_DEFER);
} else {
return tegra_hv_data;
}
}
const struct ivc_info_page *tegra_hv_get_ivc_info(void)
{
struct tegra_hv_data *hvd = get_hvd();
if (IS_ERR(hvd))
return (void *)hvd;
else
return tegra_hv_data->info;
}
EXPORT_SYMBOL(tegra_hv_get_ivc_info);
int tegra_hv_get_vmid(void)
{
struct tegra_hv_data *hvd = get_hvd();
if (IS_ERR(hvd))
return -1;
else
return hvd->guestid;
}
EXPORT_SYMBOL(tegra_hv_get_vmid);
static void ivc_handle_notification(struct hv_ivc *ivc)
{
struct tegra_hv_ivc_cookie *ivck = &ivc->cookie;
/* This function should only be used when callbacks are specified. */
BUG_ON(!ivc->cookie_ops);
/* there are data in the queue, callback */
if (ivc->cookie_ops->rx_rdy && tegra_ivc_can_read(&ivc->ivc))
ivc->cookie_ops->rx_rdy(ivck);
/* there is space in the queue to write, callback */
if (ivc->cookie_ops->tx_rdy && tegra_ivc_can_write(&ivc->ivc))
ivc->cookie_ops->tx_rdy(ivck);
}
static irqreturn_t ivc_dev_cookie_irq_handler(int irq, void *data)
{
struct hv_ivc *ivcd = data;
ivc_handle_notification(ivcd);
return IRQ_HANDLED;
}
static void ivc_release_irq(struct hv_ivc *ivc)
{
BUG_ON(!ivc);
free_irq(ivc->irq, ivc);
}
static int ivc_request_cookie_irq(struct hv_ivc *ivcd)
{
return request_irq(ivcd->irq, ivc_dev_cookie_irq_handler, 0,
ivcd->name, ivcd);
}
static int tegra_hv_add_ivc(struct tegra_hv_data *hvd,
const struct tegra_hv_queue_data *qd, uint32_t index)
{
struct hv_ivc *ivc;
int ret;
int rx_first;
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP)
struct iosys_map rx_map, tx_map;
#else
uintptr_t rx_base, tx_base;
#endif
uint32_t i;
struct irq_data *d;
uint64_t va_offset;
ivc = &hvd->ivc_devs[qd->id];
BUG_ON(ivc->valid);
ivc->valid = 1;
ivc->hvd = hvd;
ivc->qd = qd;
if (qd->peers[0] == hvd->guestid)
ivc->other_guestid = qd->peers[1];
else if (qd->peers[1] == hvd->guestid)
ivc->other_guestid = qd->peers[0];
else
BUG();
/*
* Locate the guest_ivc_info representing the remote guest accessed
* through this channel.
*/
for (i = 0; i < hvd->info->nr_areas; i++) {
if (hvd->info->areas[i].guest == ivc->other_guestid) {
ivc->givci = &hvd->guest_ivc_info[i];
ivc->area = &hvd->info->areas[i];
break;
}
}
BUG_ON(i == hvd->info->nr_areas);
BUG_ON(ivc->givci->shmem == 0);
mutex_init(&ivc->lock);
if (qd->peers[0] == qd->peers[1]) {
/*
* The queue ids of loopback queues are always consecutive, so
* the even-numbered one receives in the first area.
*/
rx_first = (qd->id & 1) == 0;
} else {
rx_first = hvd->guestid == qd->peers[0];
}
BUG_ON(qd->offset >= ivc->givci->length);
BUG_ON(qd->offset + qd->size * 2 > ivc->givci->length);
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP)
rx_map.is_iomem = false;
tx_map.is_iomem = false;
if (rx_first) {
rx_map.vaddr = (void *)ivc->givci->shmem + qd->offset;
tx_map.vaddr = (void *)ivc->givci->shmem + qd->offset + qd->size;
} else {
tx_map.vaddr = (void *)ivc->givci->shmem + qd->offset;
rx_map.vaddr = (void *)ivc->givci->shmem + qd->offset + qd->size;
}
#else
if (rx_first) {
rx_base = ivc->givci->shmem + qd->offset;
tx_base = ivc->givci->shmem + qd->offset + qd->size;
} else {
tx_base = ivc->givci->shmem + qd->offset;
rx_base = ivc->givci->shmem + qd->offset + qd->size;
}
#endif
ret = snprintf(ivc->name, sizeof(ivc->name), "ivc%u", qd->id);
if (ret < 0) {
return -EINVAL;
}
ivc->irq = of_irq_get(hvd->dev, index);
if (ivc->irq < 0) {
ERR("Unable to get irq for ivc%u\n", qd->id);
return ivc->irq;
}
d = irq_get_irq_data(ivc->irq);
if (!d) {
ERR("Failed to get data for irq %d (ivc%u)\n", ivc->irq,
qd->id);
return -ENODEV;
}
if (qd->msi_ipa != 0U) {
if (WARN_ON(ivc_notify.msi_region_size == 0UL))
return -EINVAL;
if (WARN_ON(!(qd->msi_ipa >= ivc_notify.msi_region_base_ipa &&
qd->msi_ipa <= ivc_notify.msi_region_end_ipa))) {
return -EINVAL;
}
va_offset = qd->msi_ipa - ivc_notify.msi_region_base_ipa;
ivc->cookie.notify_va =
(uint32_t *)(ivc_notify.msi_region_base_va +
va_offset);
} else if (qd->trap_ipa != 0U) {
if (WARN_ON(ivc_notify.trap_region_size == 0UL))
return -EINVAL;
if (WARN_ON(!(qd->trap_ipa >= ivc_notify.trap_region_base_ipa &&
qd->trap_ipa <= ivc_notify.trap_region_end_ipa))) {
return -EINVAL;
}
va_offset = qd->trap_ipa - ivc_notify.trap_region_base_ipa;
ivc->cookie.notify_va =
(uint32_t *)(ivc_notify.trap_region_base_va +
va_offset);
} else {
if (WARN_ON(ivc->cookie.notify_va == NULL))
return -EINVAL;
}
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP)
INFO("adding ivc%u: rx_base=%lx tx_base = %lx size=%x irq = %d (%lu)\n",
qd->id, (uintptr_t)rx_map.vaddr, (uintptr_t)tx_map.vaddr, qd->size, ivc->irq, d->hwirq);
ret = tegra_ivc_init(&ivc->ivc, NULL, &rx_map, 0, &tx_map, 0, qd->nframes,
qd->frame_size, ivc_raise_irq, ivc);
#else
INFO("adding ivc%u: rx_base=%lx tx_base = %lx size=%x irq = %d (%lu)\n",
qd->id, rx_base, tx_base, qd->size, ivc->irq, d->hwirq);
ret = tegra_ivc_init(&ivc->ivc, NULL, (void *)rx_base, 0, (void *)tx_base, 0, qd->nframes,
qd->frame_size, ivc_raise_irq, ivc);
#endif
if (ret != 0) {
ERR("failed to init ivc queue #%u\n", qd->id);
return ret;
}
/* We may have rebooted, so the channel could be active. */
ret = tegra_ivc_channel_sync(&ivc->ivc);
if (ret != 0)
return ret;
INFO("added %s\n", ivc->name);
return 0;
}
static struct hv_ivc *ivc_device_by_id(struct tegra_hv_data *hvd, uint32_t id)
{
if (id > hvd->max_qid) {
return NULL;
} else {
struct hv_ivc *ivc = &hvd->ivc_devs[id];
if (ivc->valid)
return ivc;
else
return NULL;
}
}
static void tegra_hv_ivc_cleanup(struct tegra_hv_data *hvd)
{
if (!hvd->ivc_devs)
return;
kfree(hvd->ivc_devs);
hvd->ivc_devs = NULL;
}
static void tegra_hv_cleanup(struct tegra_hv_data *hvd)
{
/*
* Destroying IVC channels in use is not supported. Once it's possible
* for IVC channels to be reserved, we no longer clean up.
*/
BUG_ON(tegra_hv_data != NULL);
kfree(hvd->mempools);
hvd->mempools = NULL;
tegra_hv_ivc_cleanup(hvd);
if (hvd->guest_ivc_info) {
uint32_t i;
BUG_ON(!hvd->info);
for (i = 0; i < hvd->info->nr_areas; i++) {
if (hvd->guest_ivc_info[i].shmem) {
iounmap((void __iomem *)hvd->guest_ivc_info[i].shmem);
hvd->guest_ivc_info[i].shmem = 0;
}
}
kfree(hvd->guest_ivc_info);
hvd->guest_ivc_info = NULL;
iounmap((void __iomem *)hvd->info);
hvd->info = NULL;
}
if (hvd->hv_class) {
class_destroy(hvd->hv_class);
hvd->hv_class = NULL;
}
}
#if defined(NV_CLASS_ATTRIBUTE_STRUCT_HAS_CONST_STRUCT_CLASS_ATTRIBUTE_ARG)
static ssize_t vmid_show(const struct class *class,
const struct class_attribute *attr, char *buf)
#else
static ssize_t vmid_show(struct class *class,
struct class_attribute *attr, char *buf)
#endif
{
struct tegra_hv_data *hvd = get_hvd();
BUG_ON(!hvd);
return snprintf(buf, PAGE_SIZE, "%d\n", hvd->guestid);
}
static CLASS_ATTR_RO(vmid);
static int tegra_hv_setup(struct tegra_hv_data *hvd)
{
const int intr_property_size = 3;
uint64_t info_page;
uint32_t i;
int ret;
uint32_t *interrupts_arr;
uint64_t ivcsize = 0;
uint64_t mpsize = 0;
hvd->dev = of_find_compatible_node(NULL, NULL, "nvidia,tegra-hv");
if (!hvd->dev) {
ERR("could not find hv node\n");
return -ENODEV;
}
ret = hyp_read_gid(&hvd->guestid);
if (ret != 0) {
ERR("Failed to read guest id\n");
return -ENODEV;
}
#if defined(NV_CLASS_CREATE_HAS_NO_OWNER_ARG) /* Linux v6.4 */
hvd->hv_class = class_create("tegra_hv");
#else
hvd->hv_class = class_create(THIS_MODULE, "tegra_hv");
#endif
if (IS_ERR(hvd->hv_class)) {
ERR("class_create() failed\n");
return PTR_ERR(hvd->hv_class);
}
ret = class_create_file(hvd->hv_class, &class_attr_vmid);
if (ret != 0) {
ERR("failed to create vmid file: %d\n", ret);
return ret;
}
ret = hyp_read_ivc_info(&info_page);
if (ret != 0) {
ERR("failed to obtain IVC info page: %d\n", ret);
return ret;
}
hvd->info = (__force struct ivc_info_page *)ioremap_cache(info_page,
IVC_INFO_PAGE_SIZE);
if (hvd->info == NULL) {
ERR("failed to map IVC info page (%llx)\n", info_page);
return -ENOMEM;
}
if ((hvd->info->nr_queues == 0U) || (hvd->info->nr_queues > PCT_MAX_NUM_IVC_QUEUES)) {
ERR("Invalid nr_queues: %u\n", hvd->info->nr_queues);
return -EINVAL;
}
if (hvd->info->nr_areas > MAX_NUM_GUESTS) {
ERR("Invalid nr_areas: %u\n", hvd->info->nr_areas);
return -EINVAL;
}
if (hvd->info->nr_mempools > PCT_MAX_NUM_MEMPOOLS) {
ERR("Invalid nr_mempools: %u\n", hvd->info->nr_mempools);
return -EINVAL;
}
/*
* Map IVC Trap MMIO Notification region
*/
ivc_notify.trap_region_base_ipa = hvd->info->trap_region_base_ipa;
ivc_notify.trap_region_size = hvd->info->trap_region_size;
if (ivc_notify.trap_region_size != 0UL) {
INFO("trap_region_base_ipa %lx: trap_region_size=%llx\n",
ivc_notify.trap_region_base_ipa,
ivc_notify.trap_region_size);
if (WARN_ON(ivc_notify.trap_region_base_ipa == 0UL))
return -EINVAL;
if (WARN_ON(ivc_notify.trap_region_base_va != 0UL))
return -EINVAL;
ivc_notify.trap_region_end_ipa =
ivc_notify.trap_region_base_ipa +
ivc_notify.trap_region_size - 1UL;
ivc_notify.trap_region_base_va =
(uintptr_t)ioremap_cache(
ivc_notify.trap_region_base_ipa,
ivc_notify.trap_region_size);
if (ivc_notify.trap_region_base_va == 0UL) {
ERR("failed to map trap ipa notification page\n");
return -ENOMEM;
}
}
/*
* Map IVC MSI Notification region
*/
ivc_notify.msi_region_base_ipa = hvd->info->msi_region_base_ipa;
ivc_notify.msi_region_size = hvd->info->msi_region_size;
if (ivc_notify.msi_region_size != 0UL) {
INFO("msi_region_base_ipa %lx: msi_region_size=%llx\n",
ivc_notify.msi_region_base_ipa,
ivc_notify.msi_region_size);
if (WARN_ON(ivc_notify.msi_region_base_ipa == 0UL))
return -EINVAL;
if (WARN_ON(ivc_notify.msi_region_base_va != 0UL))
return -EINVAL;
ivc_notify.msi_region_end_ipa = ivc_notify.msi_region_base_ipa +
ivc_notify.msi_region_size - 1UL;
ivc_notify.msi_region_base_va =
(uintptr_t)ioremap_cache(ivc_notify.msi_region_base_ipa,
ivc_notify.msi_region_size);
if (ivc_notify.msi_region_base_va == 0UL) {
ERR("failed to map msi ipa notification page\n");
return -ENOMEM;
}
}
hvd->guest_ivc_info = kzalloc(hvd->info->nr_areas *
sizeof(*hvd->guest_ivc_info), GFP_KERNEL);
if (hvd->guest_ivc_info == NULL) {
ERR("failed to allocate %u-entry givci\n",
hvd->info->nr_areas);
return -ENOMEM;
}
for (i = 0; i < hvd->info->nr_areas; i++) {
hvd->guest_ivc_info[i].shmem = (uintptr_t)ioremap_cache(
hvd->info->areas[i].pa,
hvd->info->areas[i].size);
if (hvd->guest_ivc_info[i].shmem == 0) {
ERR("can't map area for guest %u (%llx)\n",
hvd->info->areas[i].guest,
hvd->info->areas[i].pa);
return -ENOMEM;
}
hvd->guest_ivc_info[i].length = hvd->info->areas[i].size;
}
/* Do not free this, of_add_property does not copy the structure */
interrupts_arr = kmalloc(hvd->info->nr_queues * sizeof(uint32_t)
* intr_property_size, GFP_KERNEL);
if (interrupts_arr == NULL) {
ERR("failed to allocate array for interrupts property\n");
return -ENOMEM;
}
/*
* Determine the largest queue id in order to allocate a queue id-
* indexed array and device nodes, and create interrupts property
*/
hvd->max_qid = 0;
for (i = 0; i < hvd->info->nr_queues; i++) {
const struct tegra_hv_queue_data *qd =
&ivc_info_queue_array(hvd->info)[i];
if (qd->id > hvd->max_qid)
hvd->max_qid = qd->id;
/* 0 => SPI */
interrupts_arr[(i * intr_property_size)] = (__force uint32_t)cpu_to_be32(0);
interrupts_arr[(i * intr_property_size) + 1] =
(__force uint32_t)cpu_to_be32(qd->irq - 32); /* Id in SPI namespace */
/* 0x1 == low-to-high edge */
interrupts_arr[(i * intr_property_size) + 2] = (__force uint32_t)cpu_to_be32(0x1);
}
interrupts_prop.length =
hvd->info->nr_queues * sizeof(uint32_t) * intr_property_size;
interrupts_prop.value = interrupts_arr;
if (of_add_property(hvd->dev, &interrupts_prop)) {
ERR("failed to add interrupts property\n");
kfree(interrupts_arr);
return -EACCES;
}
hvd->ivc_devs = kzalloc((hvd->max_qid + 1) * sizeof(*hvd->ivc_devs),
GFP_KERNEL);
if (hvd->ivc_devs == NULL) {
ERR("failed to allocate %u-entry ivc_devs array\n",
hvd->info->nr_queues);
return -ENOMEM;
}
/* instantiate the IVC */
for (i = 0; i < hvd->info->nr_queues; i++) {
const struct tegra_hv_queue_data *qd =
&ivc_info_queue_array(hvd->info)[i];
ret = tegra_hv_add_ivc(hvd, qd, i);
if (ret != 0) {
ERR("failed to add queue #%u\n", qd->id);
return ret;
}
ivcsize += qd->size;
BUG_ON(ivcsize < qd->size);
}
hvd->mempools =
kzalloc(hvd->info->nr_mempools * sizeof(*hvd->mempools),
GFP_KERNEL);
if (hvd->mempools == NULL) {
ERR("failed to allocate %u-entry mempools array\n",
hvd->info->nr_mempools);
return -ENOMEM;
}
/* Initialize mempools. */
for (i = 0; i < hvd->info->nr_mempools; i++) {
const struct ivc_mempool *mpd =
&ivc_info_mempool_array(hvd->info)[i];
struct tegra_hv_ivm_cookie *ivmk = &hvd->mempools[i].ivmk;
hvd->mempools[i].mpd = mpd;
mutex_init(&hvd->mempools[i].lock);
ivmk->ipa = mpd->pa;
ivmk->size = mpd->size;
ivmk->peer_vmid = mpd->peer_vmid;
mpsize += mpd->size;
BUG_ON(mpsize < mpd->size);
INFO("added mempool %u: ipa=%llx size=%llx peer=%u\n",
mpd->id, mpd->pa, mpd->size, mpd->peer_vmid);
}
INFO("Memory usage: ivc:0x%llx mempool=0x%llx\n", ivcsize, mpsize);
return 0;
}
static int ivc_dump(struct hv_ivc *ivc)
{
INFO("IVC#%d: IRQ=%d(%d) nframes=%d frame_size=%d offset=%d\n",
ivc->qd->id, ivc->irq, ivc->qd->irq,
ivc->qd->nframes, ivc->qd->frame_size, ivc->qd->offset);
return 0;
}
struct tegra_hv_ivc_cookie *tegra_hv_ivc_reserve(struct device_node *dn,
int id, const struct tegra_hv_ivc_ops *ops)
{
struct tegra_hv_data *hvd = get_hvd();
struct hv_ivc *ivc;
struct tegra_hv_ivc_cookie *ivck;
int ret;
if (IS_ERR(hvd))
return (void *)hvd;
ivc = ivc_device_by_id(hvd, id);
if (ivc == NULL)
return ERR_PTR(-ENODEV);
mutex_lock(&ivc->lock);
if (ivc->reserved) {
ret = -EBUSY;
} else {
ivc->reserved = 1;
ret = 0;
}
mutex_unlock(&ivc->lock);
if (ret != 0)
return ERR_PTR(ret);
ivc->cookie_ops = ops;
ivck = &ivc->cookie;
ivck->irq = ivc->irq;
ivck->peer_vmid = ivc->other_guestid;
ivck->nframes = ivc->qd->nframes;
ivck->frame_size = ivc->qd->frame_size;
if (ivc->cookie_ops) {
ivc_handle_notification(ivc);
/* request our irq */
ret = ivc_request_cookie_irq(ivc);
if (ret) {
mutex_lock(&ivc->lock);
BUG_ON(!ivc->reserved);
ivc->reserved = 0;
mutex_unlock(&ivc->lock);
return ERR_PTR(ret);
}
}
/* return pointer to the cookie */
return ivck;
}
EXPORT_SYMBOL(tegra_hv_ivc_reserve);
void tegra_hv_ivc_notify(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc;
if (ivck == NULL)
return;
ivc = cookie_to_ivc_dev(ivck);
if (WARN_ON(!ivc->cookie.notify_va))
return;
*ivc->cookie.notify_va = ivc->qd->raise_irq;
}
EXPORT_SYMBOL(tegra_hv_ivc_notify);
int tegra_hv_ivc_get_info(struct tegra_hv_ivc_cookie *ivck, uint64_t *pa,
uint64_t *size)
{
struct hv_ivc *ivc;
int ret;
if (ivck == NULL)
return -EINVAL;
ivc = cookie_to_ivc_dev(ivck);
mutex_lock(&ivc->lock);
if (ivc->reserved) {
*pa = ivc->area->pa;
*size = ivc->area->size;
ret = 0;
} else {
ret = -EINVAL;
}
mutex_unlock(&ivc->lock);
return ret;
}
EXPORT_SYMBOL(tegra_hv_ivc_get_info);
int tegra_hv_ivc_unreserve(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc;
int ret;
if (ivck == NULL)
return -EINVAL;
ivc = cookie_to_ivc_dev(ivck);
mutex_lock(&ivc->lock);
if (ivc->reserved) {
if (ivc->cookie_ops)
ivc_release_irq(ivc);
ivc->cookie_ops = NULL;
ivc->reserved = 0;
ret = 0;
} else {
ret = -EINVAL;
}
mutex_unlock(&ivc->lock);
return ret;
}
EXPORT_SYMBOL(tegra_hv_ivc_unreserve);
int tegra_hv_ivc_write(struct tegra_hv_ivc_cookie *ivck, const void *buf,
int size)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_write(&ivc->ivc, NULL, buf, size);
}
EXPORT_SYMBOL(tegra_hv_ivc_write);
int tegra_hv_ivc_write_user(struct tegra_hv_ivc_cookie *ivck, const void __user *buf,
int size)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_write(&ivc->ivc, buf, NULL, size);
}
EXPORT_SYMBOL(tegra_hv_ivc_write_user);
int tegra_hv_ivc_read(struct tegra_hv_ivc_cookie *ivck, void *buf, int size)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_read(&ivc->ivc, NULL, buf, size);
}
EXPORT_SYMBOL(tegra_hv_ivc_read);
int tegra_hv_ivc_read_user(struct tegra_hv_ivc_cookie *ivck, void __user *buf, int size)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_read(&ivc->ivc, buf, NULL, size);
}
EXPORT_SYMBOL(tegra_hv_ivc_read_user);
#ifndef CONFIG_BUILD_PROD
int tegra_hv_ivc_read_peek(struct tegra_hv_ivc_cookie *ivck, void *buf,
int off, int count)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_read_peek(&ivc->ivc, NULL, buf, off, count);
}
EXPORT_SYMBOL(tegra_hv_ivc_read_peek);
#endif
int tegra_hv_ivc_can_read(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_can_read(&ivc->ivc);
}
EXPORT_SYMBOL(tegra_hv_ivc_can_read);
int tegra_hv_ivc_can_write(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_can_write(&ivc->ivc);
}
EXPORT_SYMBOL(tegra_hv_ivc_can_write);
int tegra_hv_ivc_tx_empty(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP)
return tegra_ivc_empty(&ivc->ivc, &ivc->ivc.tx.map);
#else
return tegra_ivc_empty(&ivc->ivc, ivc->ivc.tx.channel);
#endif
}
EXPORT_SYMBOL(tegra_hv_ivc_tx_empty);
#ifndef CONFIG_BUILD_PROD
uint32_t tegra_hv_ivc_tx_frames_available(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP)
return tegra_ivc_frames_available(&ivc->ivc, &ivc->ivc.tx.map);
#else
return tegra_ivc_frames_available(&ivc->ivc, ivc->ivc.tx.channel);
#endif
}
EXPORT_SYMBOL(tegra_hv_ivc_tx_frames_available);
int tegra_hv_ivc_dump(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return ivc_dump(ivc);
}
EXPORT_SYMBOL(tegra_hv_ivc_dump);
#endif
void *tegra_hv_ivc_read_get_next_frame(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP)
struct iosys_map map;
int err;
err = tegra_ivc_read_get_next_frame(&ivc->ivc, &map);
if (err)
return ERR_PTR(err);
return map.vaddr;
#else
return tegra_ivc_read_get_next_frame(&ivc->ivc);
#endif
}
EXPORT_SYMBOL(tegra_hv_ivc_read_get_next_frame);
void *tegra_hv_ivc_write_get_next_frame(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP)
struct iosys_map map;
int err;
err = tegra_ivc_write_get_next_frame(&ivc->ivc, &map);
if (err)
return ERR_PTR(err);
return map.vaddr;
#else
return tegra_ivc_write_get_next_frame(&ivc->ivc);
#endif
}
EXPORT_SYMBOL(tegra_hv_ivc_write_get_next_frame);
int tegra_hv_ivc_write_advance(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_write_advance(&ivc->ivc);
}
EXPORT_SYMBOL(tegra_hv_ivc_write_advance);
int tegra_hv_ivc_read_advance(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_read_advance(&ivc->ivc);
}
EXPORT_SYMBOL(tegra_hv_ivc_read_advance);
struct tegra_ivc *tegra_hv_ivc_convert_cookie(struct tegra_hv_ivc_cookie *ivck)
{
return &cookie_to_ivc_dev(ivck)->ivc;
}
EXPORT_SYMBOL(tegra_hv_ivc_convert_cookie);
struct tegra_hv_ivm_cookie *tegra_hv_mempool_reserve(unsigned id)
{
uint32_t i;
struct hv_mempool *mempool;
int reserved;
if (!tegra_hv_data)
return ERR_PTR(-EPROBE_DEFER);
/* Locate a mempool with matching id. */
for (i = 0; i < tegra_hv_data->info->nr_mempools; i++) {
mempool = &tegra_hv_data->mempools[i];
if (mempool->mpd->id == id)
break;
}
if (i == tegra_hv_data->info->nr_mempools)
return ERR_PTR(-ENODEV);
mutex_lock(&mempool->lock);
reserved = mempool->reserved;
mempool->reserved = 1;
mutex_unlock(&mempool->lock);
return reserved ? ERR_PTR(-EBUSY) : &mempool->ivmk;
}
EXPORT_SYMBOL(tegra_hv_mempool_reserve);
int tegra_hv_mempool_unreserve(struct tegra_hv_ivm_cookie *ivmk)
{
int reserved;
struct hv_mempool *mempool = container_of(ivmk, struct hv_mempool,
ivmk);
mutex_lock(&mempool->lock);
reserved = mempool->reserved;
mempool->reserved = 0;
mutex_unlock(&mempool->lock);
return reserved ? 0 : -EINVAL;
}
EXPORT_SYMBOL(tegra_hv_mempool_unreserve);
int tegra_hv_ivc_channel_notified(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
return tegra_ivc_notified(&ivc->ivc);
}
EXPORT_SYMBOL(tegra_hv_ivc_channel_notified);
void tegra_hv_ivc_channel_reset(struct tegra_hv_ivc_cookie *ivck)
{
struct hv_ivc *ivc = cookie_to_ivc_dev(ivck);
if (ivc->cookie_ops) {
ERR("reset unsupported with callbacks");
BUG();
}
tegra_ivc_reset(&ivc->ivc);
}
EXPORT_SYMBOL(tegra_hv_ivc_channel_reset);
static int tegra_hv_probe(struct platform_device *pdev)
{
struct tegra_hv_data *hvd;
int ret;
if (!is_tegra_hypervisor_mode())
return -ENODEV;
hvd = kzalloc(sizeof(*hvd), GFP_KERNEL);
if (!hvd) {
ERR("failed to allocate hvd\n");
return -ENOMEM;
}
ret = tegra_hv_setup(hvd);
if (ret != 0) {
tegra_hv_cleanup(hvd);
kfree(hvd);
return ret;
}
/*
* Ensure that all contents of hvd are visible before they are visible
* to other threads.
*/
smp_wmb();
BUG_ON(tegra_hv_data);
tegra_hv_data = hvd;
INFO("tegra_hv driver probed successfully\n");
return 0;
}
static int tegra_hv_remove(struct platform_device *pdev)
{
if (!is_tegra_hypervisor_mode())
return 0;
tegra_hv_cleanup(tegra_hv_data);
kfree(tegra_hv_data);
tegra_hv_data = NULL;
INFO("tegra_hv driver removed successfully\n");
return 0;
}
static const struct of_device_id tegra_hv_match[] = {
{ .compatible = "nvidia,tegra-hv", },
{},
};
#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
static void tegra_hv_remove_wrapper(struct platform_device *pdev)
{
tegra_hv_remove(pdev);
}
#else
static int tegra_hv_remove_wrapper(struct platform_device *pdev)
{
return tegra_hv_remove(pdev);
}
#endif
static struct platform_driver tegra_hv_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(tegra_hv_match),
},
.probe = tegra_hv_probe,
.remove = tegra_hv_remove_wrapper,
};
static int __init tegra_hv_init(void)
{
int ret;
ret = platform_driver_register(&tegra_hv_driver);
if (ret)
pr_err("Error: tegra_hv driver registration failed\n");
return ret;
}
static void __exit tegra_hv_exit(void)
{
platform_driver_unregister(&tegra_hv_driver);
}
module_init(tegra_hv_init);
module_exit(tegra_hv_exit);
MODULE_AUTHOR("Manish Bhardwaj <mbhardwaj@nvidia.com>");
MODULE_DESCRIPTION("Hypervisor Driver");
MODULE_LICENSE("GPL v2");