mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
Bug 4036670 Change-Id: I619baafc4625cb604d87c0fe0e0ec4368bc0b837 Signed-off-by: Liang Cheng <licheng@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2878778 GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com> Reviewed-by: Bitan Biswas <bbiswas@nvidia.com> Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
401 lines
9.3 KiB
C
401 lines
9.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (c) 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
|
|
#include <linux/ioctl.h>
|
|
#include <linux/types.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/mailbox_client.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/types.h>
|
|
#include <linux/completion.h>
|
|
#include "tegra23x_psc.h"
|
|
|
|
/* EXT_CFG register offset */
|
|
#define EXT_CFG_SIDTABLE 0x0
|
|
#define EXT_CFG_SIDCONFIG 0x4
|
|
|
|
#define MBOX_MSG_LEN 64
|
|
|
|
#define RX_READY 1
|
|
#define RX_IDLE 0
|
|
|
|
/* Max block period in ms before TX is assumed failure. */
|
|
#define DEFAULT_TX_TIMEOUT 2000
|
|
|
|
/* 256MB max size to use for dma_alloc* */
|
|
#define MAX_SHARED_MEM (256 * 1024 * 1024U)
|
|
|
|
struct xfer_info {
|
|
__u32 opcode[2];
|
|
void __user *tx_buf;
|
|
void __user *rx_buf;
|
|
|
|
__u32 tx_size;
|
|
__u32 rx_size;
|
|
|
|
__u8 out[MBOX_MSG_LEN];
|
|
};
|
|
|
|
union mbox_msg {
|
|
struct {
|
|
u32 opcode[2];
|
|
u32 tx_size;
|
|
u32 rx_size;
|
|
u64 tx_iova;
|
|
u64 rx_iova;
|
|
};
|
|
u32 data[16];
|
|
};
|
|
|
|
#define PSCDBG_IOCTL_BASE 'P'
|
|
#define PSCIOC_XFER_DATA _IOWR(PSCDBG_IOCTL_BASE, 0, struct xfer_info)
|
|
|
|
struct psc_debug_dev {
|
|
struct mutex lock;
|
|
struct platform_device *pdev;
|
|
struct mbox_client cl;
|
|
struct mbox_chan *chan;
|
|
struct completion rx_complete;
|
|
|
|
u8 rx_msg[MBOX_MSG_LEN];
|
|
struct mbox_controller *mbox; /* our mbox controller */
|
|
|
|
bool is_cfg_inited; /* did we initialize SIDTABLE, etc? */
|
|
};
|
|
|
|
static struct psc_debug_dev psc_debug;
|
|
static struct dentry *debugfs_root;
|
|
|
|
#define NV(x) "nvidia," #x
|
|
static int
|
|
setup_extcfg(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
void __iomem *base;
|
|
u32 value;
|
|
|
|
/* second mailbox address */
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "extcfg");
|
|
/* we have res == 0 in case of ACPI and not DT */
|
|
if (res == NULL)
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(base))
|
|
return -EINVAL;
|
|
|
|
dev_info(&pdev->dev, "ext_cfg base:%p\n", base);
|
|
|
|
if (!device_property_read_u8_array(&pdev->dev, NV(sidtable),
|
|
(u8 *)&value, sizeof(value))) {
|
|
dev_dbg(&pdev->dev, "sidtable:%08x\n", value);
|
|
writel0(value, base + EXT_CFG_SIDTABLE); /* PSC_EXT_CFG_SIDTABLE_VM0_0 */
|
|
}
|
|
|
|
if (!device_property_read_u32(&pdev->dev, NV(sidconfig), &value)) {
|
|
dev_dbg(&pdev->dev, "sidcfg:%08x\n", value);
|
|
writel0(value, base + EXT_CFG_SIDCONFIG); /* PSC_EXT_CFG_SIDCONFIG_VM0_0 */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int psc_debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct mbox_chan *chan;
|
|
struct psc_debug_dev *dbg = inode->i_private;
|
|
struct platform_device *pdev = dbg->pdev;
|
|
int ret = 0;
|
|
|
|
if (mutex_lock_interruptible(&dbg->lock))
|
|
return -ERESTARTSYS;
|
|
|
|
if (dbg->is_cfg_inited == false) {
|
|
dbg->is_cfg_inited = true;
|
|
setup_extcfg(pdev);
|
|
}
|
|
|
|
file->private_data = dbg;
|
|
|
|
chan = psc_mbox_request_channel0(dbg->mbox, &dbg->cl);
|
|
if (IS_ERR(chan) && (PTR_ERR(chan) != -EPROBE_DEFER)) {
|
|
dev_err(&pdev->dev, "failed to get channel, err %lx\n",
|
|
PTR_ERR(chan));
|
|
ret = PTR_ERR(chan);
|
|
goto return_unlock;
|
|
}
|
|
|
|
dbg->chan = chan;
|
|
init_completion(&dbg->rx_complete);
|
|
nonseekable_open(inode, file);
|
|
|
|
return_unlock:
|
|
mutex_unlock(&dbg->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int psc_debug_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct psc_debug_dev *dbg = file->private_data;
|
|
|
|
mutex_lock(&dbg->lock);
|
|
|
|
mbox_free_channel(dbg->chan);
|
|
|
|
file->private_data = NULL;
|
|
|
|
mutex_unlock(&dbg->lock);
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t psc_debug_read(struct file *file, char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct psc_debug_dev *dbg = file->private_data;
|
|
ssize_t ret;
|
|
loff_t pos = 0;
|
|
|
|
if (count > MBOX_MSG_LEN)
|
|
return -EINTR;
|
|
|
|
mutex_lock(&dbg->lock);
|
|
|
|
ret = simple_read_from_buffer(buffer, count, &pos,
|
|
dbg->rx_msg, min_t(size_t, count, MBOX_MSG_LEN));
|
|
*ppos += pos;
|
|
|
|
mutex_unlock(&dbg->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int send_msg_block(struct psc_debug_dev *dbg, void *tx)
|
|
{
|
|
int ret;
|
|
|
|
reinit_completion(&dbg->rx_complete);
|
|
|
|
ret = mbox_send_message(dbg->chan, tx);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
mbox_client_txdone(dbg->chan, 0);
|
|
ret = wait_for_completion_timeout(&dbg->rx_complete,
|
|
msecs_to_jiffies(dbg->cl.tx_tout));
|
|
if (ret == 0) {
|
|
pr_info("%s:%d wait_for_completion_timeout timed out!\n", __func__, __LINE__);
|
|
return -ETIME;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t psc_debug_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct psc_debug_dev *dbg = file->private_data;
|
|
struct platform_device *pdev = dbg->pdev;
|
|
u8 tx_buf[MBOX_MSG_LEN] = { 0 };
|
|
ssize_t ret;
|
|
|
|
if (count > MBOX_MSG_LEN) {
|
|
dev_err(&pdev->dev, "write size > MBOX_MSG_LEN\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&dbg->lock);
|
|
if (copy_from_user(tx_buf, buffer, count)) {
|
|
dev_err(&pdev->dev, "copy_from_user() error!\n");
|
|
ret = -EFAULT;
|
|
goto return_unlock;
|
|
}
|
|
ret = send_msg_block(dbg, tx_buf);
|
|
|
|
return_unlock:
|
|
mutex_unlock(&dbg->lock);
|
|
return ret < 0 ? ret : count;
|
|
}
|
|
|
|
static long xfer_data(struct file *file, char __user *data)
|
|
{
|
|
struct psc_debug_dev *dbg = file->private_data;
|
|
struct platform_device *pdev = dbg->pdev;
|
|
struct device *dev = &pdev->dev;
|
|
void *tx_virt = NULL;
|
|
void *rx_virt = NULL;
|
|
dma_addr_t tx_phys = 0;
|
|
dma_addr_t rx_phys = 0;
|
|
long ret = 0;
|
|
union mbox_msg msg = {};
|
|
struct xfer_info info;
|
|
struct xfer_info __user *ptr_xfer = (struct xfer_info __user *)data;
|
|
|
|
if (copy_from_user(&info, data, sizeof(struct xfer_info))) {
|
|
dev_err(&pdev->dev, "failed to copy data.\n");
|
|
ret = -EFAULT;
|
|
}
|
|
|
|
dev_dbg(dev, "opcode[%x %x]\n", info.opcode[0], info.opcode[1]);
|
|
dev_dbg(dev, "tx[%p, size:%u], rx[%p, size:%u]\n",
|
|
info.tx_buf, info.tx_size, info.rx_buf, info.rx_size);
|
|
|
|
if (info.tx_size > MAX_SHARED_MEM || info.rx_size > MAX_SHARED_MEM)
|
|
return -ENOMEM;
|
|
|
|
if (info.tx_buf && info.tx_size > 0) {
|
|
tx_virt = dma_alloc_coherent(dev, info.tx_size,
|
|
&tx_phys, GFP_KERNEL);
|
|
if (tx_virt == NULL || tx_phys == 0) {
|
|
dev_err(dev, "dma_alloc_coherent() failed!\n");
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (copy_from_user(tx_virt, info.tx_buf, info.tx_size)) {
|
|
dev_err(dev, "failed to copy data.\n");
|
|
ret = -EFAULT;
|
|
goto free_rx;
|
|
}
|
|
dma_sync_single_for_device(dev, tx_phys,
|
|
info.tx_size, DMA_TO_DEVICE);
|
|
}
|
|
|
|
if (info.rx_buf && info.rx_size > 0) {
|
|
rx_virt = dma_alloc_coherent(dev, info.rx_size,
|
|
&rx_phys, GFP_KERNEL);
|
|
if (rx_virt == NULL || rx_phys == 0) {
|
|
dev_err(dev, "dma_alloc_coherent() failed!\n");
|
|
ret = -ENOMEM;
|
|
goto free_tx;
|
|
}
|
|
}
|
|
dev_dbg(dev, "tx_virt:%p, tx_phys: %p\n", tx_virt, (void *)tx_phys);
|
|
dev_dbg(dev, "rx_virt:%p, rx_phys: %p\n", rx_virt, (void *)rx_phys);
|
|
|
|
msg.opcode[0] = info.opcode[0];
|
|
msg.opcode[1] = info.opcode[1];
|
|
msg.tx_iova = tx_phys;
|
|
msg.rx_iova = rx_phys;
|
|
msg.tx_size = info.tx_size;
|
|
msg.rx_size = info.rx_size;
|
|
|
|
ret = send_msg_block(dbg, &msg);
|
|
if (ret != 0)
|
|
goto free_rx;
|
|
|
|
/* copy mbox payload */
|
|
if (copy_to_user(&ptr_xfer->out[0],
|
|
&dbg->rx_msg[0], sizeof(dbg->rx_msg))) {
|
|
dev_err(dev, "failed to mbox out data.\n");
|
|
ret = -EFAULT;
|
|
goto free_rx;
|
|
}
|
|
|
|
if (rx_phys && info.rx_size > 0) {
|
|
dma_sync_single_for_cpu(dev, rx_phys,
|
|
info.rx_size, DMA_BIDIRECTIONAL);
|
|
|
|
if (copy_to_user(info.rx_buf, rx_virt, msg.rx_size)) {
|
|
dev_err(dev, "failed to copy_to_user.\n");
|
|
ret = -EFAULT;
|
|
goto free_rx;
|
|
}
|
|
}
|
|
|
|
free_rx:
|
|
if (rx_phys)
|
|
dma_free_coherent(dev, info.rx_size, rx_virt, rx_phys);
|
|
free_tx:
|
|
if (tx_phys)
|
|
dma_free_coherent(dev, info.tx_size, tx_virt, tx_phys);
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static long
|
|
psc_debug_ioctl(struct file *file, unsigned int cmd, unsigned long data)
|
|
{
|
|
struct psc_debug_dev *dbg = file->private_data;
|
|
long ret = -ENOIOCTLCMD;
|
|
|
|
mutex_lock(&dbg->lock);
|
|
|
|
switch (cmd) {
|
|
case PSCIOC_XFER_DATA:
|
|
ret = xfer_data(file, (char __user *)data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&dbg->lock);
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations psc_debug_fops = {
|
|
.open = psc_debug_open,
|
|
.read = psc_debug_read,
|
|
.write = psc_debug_write,
|
|
.release = psc_debug_release,
|
|
.unlocked_ioctl = psc_debug_ioctl,
|
|
};
|
|
|
|
static void psc_chan_rx_callback(struct mbox_client *c, void *msg)
|
|
{
|
|
struct device *dev = c->dev;
|
|
struct psc_debug_dev *dbg = container_of(c, struct psc_debug_dev, cl);
|
|
|
|
dev_dbg(dev, "%s\n", __func__);
|
|
|
|
memcpy(dbg->rx_msg, msg, MBOX_MSG_LEN);
|
|
complete(&dbg->rx_complete);
|
|
}
|
|
|
|
int psc_debugfs_create(struct platform_device *pdev, struct mbox_controller *mbox)
|
|
{
|
|
struct psc_debug_dev *dbg = &psc_debug;
|
|
struct device *dev = &pdev->dev;
|
|
|
|
if (!debugfs_initialized()) {
|
|
dev_err(dev, "debugfs is not initialized\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
debugfs_root = debugfs_create_dir("psc", NULL);
|
|
if (debugfs_root == NULL) {
|
|
dev_err(dev, "failed to create psc debugfs\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dbg->cl.dev = dev;
|
|
dbg->cl.rx_callback = psc_chan_rx_callback;
|
|
dbg->cl.tx_block = false;
|
|
dbg->cl.tx_tout = DEFAULT_TX_TIMEOUT;
|
|
dbg->cl.knows_txdone = false;
|
|
dbg->pdev = pdev;
|
|
dbg->mbox = mbox; /* our controller */
|
|
dbg->is_cfg_inited = false;
|
|
|
|
mutex_init(&dbg->lock);
|
|
|
|
debugfs_create_x64("tx_timeout", 0644, debugfs_root,
|
|
(u64 *)&dbg->cl.tx_tout);
|
|
debugfs_create_file("mbox_dbg", 0600, debugfs_root,
|
|
dbg, &psc_debug_fops);
|
|
|
|
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39));
|
|
|
|
return 0;
|
|
}
|
|
|
|
void psc_debugfs_remove(struct platform_device *pdev)
|
|
{
|
|
dev_dbg(&pdev->dev, "%s\n", __func__);
|
|
|
|
mutex_destroy(&psc_debug.lock);
|
|
debugfs_remove_recursive(debugfs_root);
|
|
}
|