mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
For Linux v6.8, commit ef175b29a242 ("of: Stop circularly including
of_device.h and of_platform.h") updated the OF headers included by these
header files. This breaks the build for various drivers and so fix this
by including the headers that are actually needed for each driver.
Bug 4448428
Change-Id: I19a0d9003aa655d12c8ae50131f15a6ee4cc59f8
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3063634
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
1071 lines
25 KiB
C
1071 lines
25 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* Copyright (c) 2022-2023, 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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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", },
|
|
{},
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
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");
|