tegrakernel/kernel/nvidia/drivers/video/tegra/host/vhost/vhost.c

399 lines
9.6 KiB
C

/*
* Tegra Graphics Virtualization Host functions for HOST1X
*
* Copyright (c) 2014-2021, NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/tegra_vhost.h>
#include <linux/nvhost.h>
#include "vhost.h"
#include "../host1x/host1x.h"
static inline int vhost_comm_init(struct platform_device *pdev,
bool channel_management_in_guest)
{
size_t queue_sizes[] = { TEGRA_VHOST_QUEUE_SIZES };
unsigned int num_queues =
channel_management_in_guest ?
1 : ARRAY_SIZE(queue_sizes);
return tegra_gr_comm_init(pdev, num_queues, queue_sizes,
TEGRA_VHOST_QUEUE_CMD, num_queues);
}
static inline void vhost_comm_deinit(bool channel_management_in_guest)
{
size_t queue_sizes[] = { TEGRA_VHOST_QUEUE_SIZES };
unsigned int num_queues =
channel_management_in_guest ?
1 : ARRAY_SIZE(queue_sizes);
tegra_gr_comm_deinit(TEGRA_VHOST_QUEUE_CMD, num_queues);
}
int vhost_virt_moduleid(int moduleid)
{
switch (moduleid) {
case NVHOST_MODULE_NONE:
return TEGRA_VHOST_MODULE_HOST;
case NVHOST_MODULE_ISP:
return TEGRA_VHOST_MODULE_ISP;
case (1 << 16) | NVHOST_MODULE_ISP:
return (1 << 16) | TEGRA_VHOST_MODULE_ISP;
case NVHOST_MODULE_VI:
return TEGRA_VHOST_MODULE_VI;
case (1 << 16) | NVHOST_MODULE_VI:
return (1 << 16) | TEGRA_VHOST_MODULE_VI;
case NVHOST_MODULE_MSENC:
return TEGRA_VHOST_MODULE_MSENC;
case NVHOST_MODULE_VIC:
return TEGRA_VHOST_MODULE_VIC;
case NVHOST_MODULE_NVDEC:
return TEGRA_VHOST_MODULE_NVDEC;
case NVHOST_MODULE_NVJPG:
return TEGRA_VHOST_MODULE_NVJPG;
case NVHOST_MODULE_NVDEC1:
return TEGRA_VHOST_MODULE_NVDEC1;
case NVHOST_MODULE_NVENC1:
return TEGRA_VHOST_MODULE_NVENC1;
case NVHOST_MODULE_NVCSI:
return TEGRA_VHOST_MODULE_NVCSI;
default:
pr_err("module %d not virtualized\n", moduleid);
return -1;
}
}
int vhost_moduleid_virt_to_hw(int moduleid)
{
switch (moduleid) {
case TEGRA_VHOST_MODULE_HOST:
return NVHOST_MODULE_NONE;
case TEGRA_VHOST_MODULE_ISP:
return NVHOST_MODULE_ISP;
case (1 << 16) | TEGRA_VHOST_MODULE_ISP:
return (1 << 16) | NVHOST_MODULE_ISP;
case TEGRA_VHOST_MODULE_VI:
return NVHOST_MODULE_VI;
case (1 << 16) | TEGRA_VHOST_MODULE_VI:
return (1 << 16) | NVHOST_MODULE_VI;
case TEGRA_VHOST_MODULE_MSENC:
return NVHOST_MODULE_MSENC;
case TEGRA_VHOST_MODULE_VIC:
return NVHOST_MODULE_VIC;
case TEGRA_VHOST_MODULE_NVDEC:
return NVHOST_MODULE_NVDEC;
case TEGRA_VHOST_MODULE_NVJPG:
return NVHOST_MODULE_NVJPG;
case TEGRA_VHOST_MODULE_NVCSI:
return NVHOST_MODULE_NVCSI;
default:
pr_err("unknown virtualized module %d\n", moduleid);
return -1;
}
}
static u64 vhost_virt_connect(int moduleid)
{
struct tegra_vhost_cmd_msg msg;
struct tegra_vhost_connect_params *p = &msg.params.connect;
int err;
msg.cmd = TEGRA_VHOST_CMD_CONNECT;
p->module = vhost_virt_moduleid(moduleid);
if (p->module == -1)
return 0;
err = vhost_sendrecv(&msg);
return (err || msg.ret) ? 0 : p->handle;
}
int vhost_sendrecv(struct tegra_vhost_cmd_msg *msg)
{
void *handle;
size_t size = sizeof(*msg);
size_t size_out = size;
void *data = msg;
int err;
err = tegra_gr_comm_sendrecv(tegra_gr_comm_get_server_vmid(),
TEGRA_VHOST_QUEUE_CMD, &handle, &data, &size);
if (!err) {
WARN_ON(size < size_out);
memcpy(msg, data, size_out);
tegra_gr_comm_release(handle);
}
return err;
}
static void vhost_fake_debug_show_channel_cdma(struct nvhost_master *m,
struct nvhost_channel *ch, struct output *o, int chid)
{
}
static void vhost_fake_debug_show_channel_fifo(struct nvhost_master *m,
struct nvhost_channel *ch, struct output *o, int chid)
{
}
static void vhost_fake_debug_show_mlocks(struct nvhost_master *m,
struct output *o)
{
}
void vhost_init_host1x_debug_ops(struct nvhost_debug_ops *ops)
{
/* Does not support hw debug on VM side */
ops->show_channel_cdma = vhost_fake_debug_show_channel_cdma;
ops->show_channel_fifo = vhost_fake_debug_show_channel_fifo;
ops->show_mlocks = vhost_fake_debug_show_mlocks;
}
int nvhost_virt_init(struct platform_device *dev, int moduleid)
{
int err = 0;
bool channel_management_in_guest = false;
struct nvhost_master *host = nvhost_get_host(dev);
struct nvhost_virt_ctx *virt_ctx = kzalloc(sizeof(*virt_ctx), GFP_KERNEL);
if (!virt_ctx)
return -ENOMEM;
if (!host) {
err = -EAGAIN;
goto fail;
}
if (host->info.vmserver_owns_engines)
channel_management_in_guest = true;
/* If host1x, init comm */
if (moduleid == NVHOST_MODULE_NONE) {
err = vhost_comm_init(dev, channel_management_in_guest);
if (err) {
dev_err(&dev->dev, "failed to init comm interface\n");
goto fail;
}
}
virt_ctx->handle = vhost_virt_connect(moduleid);
if (!virt_ctx->handle) {
dev_err(&dev->dev,
"failed to connect to server node\n");
if (moduleid == NVHOST_MODULE_NONE)
vhost_comm_deinit(channel_management_in_guest);
err = -ENOMEM;
goto fail;
}
nvhost_set_virt_data(dev, virt_ctx);
return 0;
fail:
kfree(virt_ctx);
return err;
}
void nvhost_virt_deinit(struct platform_device *dev)
{
struct nvhost_virt_ctx *virt_ctx = nvhost_get_virt_data(dev);
struct nvhost_master *host = nvhost_get_host(dev);
if (virt_ctx) {
/* FIXME: add virt disconnect */
vhost_comm_deinit(host->info.vmserver_owns_engines);
kfree(virt_ctx);
}
}
int vhost_suspend(struct platform_device *pdev)
{
struct tegra_vhost_cmd_msg msg;
struct nvhost_virt_ctx *ctx = nvhost_get_virt_data(pdev);
if (!ctx)
return 0;
msg.cmd = TEGRA_VHOST_CMD_SUSPEND;
msg.handle = ctx->handle;
return vhost_sendrecv(&msg);
}
int vhost_resume(struct platform_device *pdev)
{
struct tegra_vhost_cmd_msg msg;
struct nvhost_virt_ctx *ctx = nvhost_get_virt_data(pdev);
if (!ctx)
return 0;
msg.cmd = TEGRA_VHOST_CMD_RESUME;
msg.handle = ctx->handle;
return vhost_sendrecv(&msg);
}
int vhost_prod_apply(struct platform_device *pdev,
unsigned int phy_mode)
{
struct tegra_vhost_cmd_msg msg;
struct nvhost_virt_ctx *ctx = nvhost_get_virt_data(pdev);
struct tegra_vhost_prod_apply_params *p =
&msg.params.prod_apply;
msg.cmd = TEGRA_VHOST_CMD_PROD_APPLY;
msg.handle = ctx->handle;
p->phy_mode = phy_mode;
return vhost_sendrecv(&msg);
}
int vhost_cil_sw_reset(struct platform_device *pdev, u32 lanes, u32 enable)
{
struct tegra_vhost_cmd_msg msg;
struct nvhost_virt_ctx *ctx = nvhost_get_virt_data(pdev);
struct tegra_vhost_cil_sw_reset_params *p =
&msg.params.cil_sw_reset;
msg.cmd = TEGRA_VHOST_CMD_CIL_SW_RESET;
msg.handle = ctx->handle;
p->lanes = lanes;
p->enable = enable;
return vhost_sendrecv(&msg);
}
static int vhost_host1x_regrdwr(u64 handle, u32 moduleid, u32 num_offsets,
u32 block_size, u32 *offs, u32 *vals, u32 write)
{
struct tegra_vhost_cmd_msg msg;
struct tegra_vhost_channel_regrdwr_params *p =
&msg.params.regrdwr;
int err;
u32 num_per_block = block_size >> 2;
u32 remaining = num_offsets * num_per_block;
u32 i, n = 0;
u32 *ptr;
msg.cmd = TEGRA_VHOST_CMD_HOST1X_REGRDWR;
msg.handle = handle;
p->moduleid = moduleid;
p->write = write;
/* For writes, fill the back end of the msg buffer with offset/value
* pairs. For reads, it's all offsets, which will be replaced by
* the returned register values.
*/
if (write) {
while (remaining > 0) {
p->count = min(remaining, REGRDWR_ARRAY_SIZE >> 1);
remaining -= p->count;
ptr = p->regs;
for (i = 0; i < p->count; i++) {
*ptr++ = *offs + n * 4;
*ptr++ = *vals++;
if (++n == num_per_block) {
offs++;
n = 0;
}
}
err = vhost_sendrecv(&msg);
if (err || msg.ret)
return -1;
}
} else {
while (remaining > 0) {
p->count = min(remaining, REGRDWR_ARRAY_SIZE);
remaining -= p->count;
ptr = p->regs;
for (i = 0; i < p->count; i++) {
*ptr++ = *offs + n * 4;
if (++n == num_per_block) {
offs++;
n = 0;
}
}
err = vhost_sendrecv(&msg);
if (err || msg.ret)
return -1;
memcpy(vals, p->regs, p->count * sizeof(u32));
vals += p->count;
}
}
return 0;
}
int vhost_rdwr_module_regs(struct platform_device *ndev, u32 num_offsets,
u32 block_size, u32 __user *offsets,
u32 __user *values, u32 write)
{
struct nvhost_device_data *pdata = platform_get_drvdata(ndev);
struct nvhost_master *nvhost_master = nvhost_get_host(ndev);
struct nvhost_virt_ctx *ctx = nvhost_get_virt_data(nvhost_master->dev);
u32 *vals, *offs;
int err;
vals = kmalloc_array(num_offsets, block_size, GFP_KERNEL);
if (!vals)
return -ENOMEM;
offs = kmalloc_array(num_offsets, sizeof(u32), GFP_KERNEL);
if (!offs) {
kfree(vals);
return -ENOMEM;
}
if (copy_from_user((void *)offs, (void __user *)offsets,
num_offsets * sizeof(u32))) {
err = -EFAULT;
goto done;
}
if (write) {
if (copy_from_user((void *)vals, (void __user *)values,
num_offsets * block_size)) {
err = -EFAULT;
goto done;
}
}
err = vhost_host1x_regrdwr(ctx->handle,
vhost_virt_moduleid(pdata->moduleid),
num_offsets, block_size, offs, vals, write);
if (err) {
err = -EFAULT;
goto done;
}
if (!write) {
if (copy_to_user((void __user *)values, (void *)vals,
num_offsets * block_size))
err = -EFAULT;
}
done:
kfree(vals);
kfree(offs);
return err;
}