tegrakernel/kernel/nvidia/drivers/platform/tegra/tegra-hv-xhci-debug.c

358 lines
8.4 KiB
C

/*
* tegra-hv-xhci-debug: Tegra Hypervisor XHCI server debug
* Copyright (c) 2016-2018, 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.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <soc/tegra/chip-id.h>
#include <linux/wait.h>
#define DRV_NAME "tegra_hv_xhci_debug"
#include <linux/tegra-ivc.h>
/* frame format is
* 0000: <size>
* 0004: <flags>
* 0008: data
*/
struct msg_header {
uint32_t size;
uint32_t flag;
};
#define HDR_SIZE (sizeof(struct msg_header))
#define FW_LOG_FILE_OPENED (1)
struct tegra_hv_xhci_debug {
struct platform_device *pdev;
struct tegra_hv_ivc_cookie *ivck;
struct dentry *debugfs_dir;
struct dentry *log_file;
unsigned long flags;
wait_queue_head_t read_wait;
spinlock_t lock;
void *rx_frame;
u8 *rx_buf;
void *tx_frame;
u8 *tx_buf;
void *buf;
int rx_cnt;
};
static ssize_t fw_log_file_read(struct file *file, char __user *buf,
size_t count, loff_t *offp)
{
struct tegra_hv_xhci_debug *tegra = file->private_data;
struct device *dev = &tegra->pdev->dev;
struct msg_header *header;
int rc;
size_t n = 0;
int s;
dev_dbg(dev, "%s can_read %d rx_cnt %d\n",
__func__, tegra_hv_ivc_can_read(tegra->ivck), tegra->rx_cnt);
while (!tegra_hv_ivc_can_read(tegra->ivck) && !tegra->rx_cnt) {
dev_dbg(dev, "%s can_read %d rx_cnt %d\n",
__func__, tegra_hv_ivc_can_read(tegra->ivck),
tegra->rx_cnt);
if (file->f_flags & O_NONBLOCK)
return -EAGAIN; /* non-blocking read */
dev_dbg(dev, "%s: nothing to read\n", __func__);
if (wait_event_interruptible(tegra->read_wait,
tegra_hv_ivc_can_read(tegra->ivck)))
return -ERESTARTSYS;
}
while (count > 0) {
if (!tegra->rx_cnt) {
if (!tegra_hv_ivc_can_read(tegra->ivck))
break;
rc = tegra_hv_ivc_read(tegra->ivck, tegra->rx_frame,
tegra->ivck->frame_size);
if (rc < 0) {
dev_err(dev, "ivc_read failied %d\n", rc);
return rc;
}
header = tegra->rx_frame;
dev_dbg(dev, "%s received %u bytes\n", __func__,
header->size);
tegra->rx_cnt = header->size;
}
s = min_t(int, count, tegra->rx_cnt);
if (s > 0) {
if (copy_to_user(&buf[n], tegra->rx_buf, s)) {
dev_warn(dev, "copy_to_user failed\n");
return -EFAULT;
}
tegra->rx_cnt -= s;
tegra->rx_buf += s;
if (tegra->rx_cnt == 0)
tegra->rx_buf = tegra->rx_frame + HDR_SIZE;
count -= s;
n += s;
} else
break;
}
dev_dbg(dev, "%s: %zu bytes\n", __func__, n);
return n;
}
static int fw_log_file_open(struct inode *inode, struct file *file)
{
struct tegra_hv_xhci_debug *tegra;
file->private_data = inode->i_private;
tegra = file->private_data;
if (test_and_set_bit(FW_LOG_FILE_OPENED, &tegra->flags)) {
dev_info(&tegra->pdev->dev, "%s: already opened\n", __func__);
return -EBUSY;
}
return 0;
}
static int fw_log_file_close(struct inode *inode, struct file *file)
{
struct tegra_hv_xhci_debug *tegra = file->private_data;
clear_bit(FW_LOG_FILE_OPENED, &tegra->flags);
return 0;
}
static const struct file_operations firmware_log_fops = {
.open = fw_log_file_open,
.release = fw_log_file_close,
.read = fw_log_file_read,
.owner = THIS_MODULE,
};
static void tegra_hv_xhci_debug_debugfs_init(struct tegra_hv_xhci_debug *tegra)
{
struct device *dev = &tegra->pdev->dev;
tegra->debugfs_dir = debugfs_create_dir("tegra_hv_xhci_debug", NULL);
if (IS_ERR_OR_NULL(tegra->debugfs_dir)) {
tegra->debugfs_dir = NULL;
dev_warn(dev, "debugfs_create_dir() for tegra_xhci failed\n");
return;
}
tegra->log_file = debugfs_create_file("firmware_log", 0444,
tegra->debugfs_dir, tegra, &firmware_log_fops);
if ((!tegra->log_file) || (tegra->log_file == ERR_PTR(-ENODEV))) {
dev_warn(dev, "debugfs_create_file() failed\n");
tegra->log_file = NULL;
}
}
static
void tegra_hv_xhci_debug_debugfs_deinit(struct tegra_hv_xhci_debug *tegra)
{
debugfs_remove(tegra->debugfs_dir);
tegra->debugfs_dir = NULL;
}
static irqreturn_t tegra_hv_xhci_debug_irq(int irq, void *data)
{
struct tegra_hv_xhci_debug *tegra = data;
struct device *dev = &tegra->pdev->dev;
dev_dbg(dev, "%s\n", __func__);
spin_lock(&tegra->lock);
/* until this function returns 0, the channel is unusable */
if (tegra_hv_ivc_channel_notified(tegra->ivck) != 0)
dev_info(dev, "tegra_hv_ivc_channel_notified failed\n");
wake_up_interruptible(&tegra->read_wait);
spin_unlock(&tegra->lock);
return IRQ_HANDLED;
}
static int tegra_hv_xhci_debug_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np, *hv_np;
struct tegra_hv_xhci_debug *tegra;
int ret;
u32 id;
if (!is_tegra_hypervisor_mode()) {
dev_info(dev, "Hypervisor is not present\n");
return -ENODEV;
}
np = dev->of_node;
if (!np) {
dev_err(dev, "No OF data\n");
return -EINVAL;
}
hv_np = of_parse_phandle(np, "ivc", 0);
if (!hv_np) {
dev_err(dev, "Failed to parse phandle of ivc prop\n");
return -EINVAL;
}
ret = of_property_read_u32_index(np, "ivc", 1, &id);
if (ret) {
dev_err(dev, "Failed to read IVC property ID\n");
of_node_put(hv_np);
return ret;
}
tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
if (!tegra)
return -ENOMEM;
tegra->ivck = tegra_hv_ivc_reserve(hv_np, id, NULL);
of_node_put(hv_np);
if (IS_ERR_OR_NULL(tegra->ivck)) {
dev_err(dev, "Failed to reserve IVC channel %d\n", id);
ret = PTR_ERR(tegra->ivck);
tegra->ivck = NULL;
return ret;
}
/* make sure the frame size is sufficient */
if (tegra->ivck->frame_size <= HDR_SIZE) {
dev_err(dev, "frame size too small to support COMM\n");
ret = -EINVAL;
goto out_unreserve;
}
dev_info(dev, "Reserved IVC channel #%d - frame_size=%d irq %d\n",
id, tegra->ivck->frame_size, tegra->ivck->irq);
/* allocate temporary frames */
tegra->buf = devm_kzalloc(dev, tegra->ivck->frame_size * 2, GFP_KERNEL);
if (!tegra->buf) {
ret = -ENOMEM;
goto out_unreserve;
}
tegra->rx_frame = tegra->buf;
tegra->rx_buf = tegra->rx_frame + HDR_SIZE;
tegra->tx_frame = tegra->rx_frame + tegra->ivck->frame_size;
tegra->tx_buf = tegra->tx_frame + HDR_SIZE;
init_waitqueue_head(&tegra->read_wait);
tegra->pdev = pdev;
platform_set_drvdata(pdev, tegra);
/*
* start the channel reset process asynchronously. until the reset
* process completes, any attempt to use the ivc channel will return
* an error (e.g., all transmits will fail.)
*/
tegra_hv_ivc_channel_reset(tegra->ivck);
spin_lock_init(&tegra->lock);
ret = devm_request_irq(dev, tegra->ivck->irq,
tegra_hv_xhci_debug_irq, 0,
dev_name(dev), tegra);
if (ret) {
dev_err(dev, "unable to request irq=%d\n", tegra->ivck->irq);
return ret;
}
tegra_hv_xhci_debug_debugfs_init(tegra);
dev_info(dev, "ready\n");
return 0;
out_unreserve:
tegra_hv_ivc_unreserve(tegra->ivck);
return ret;
}
static int tegra_hv_xhci_debug_remove(struct platform_device *pdev)
{
struct tegra_hv_xhci_debug *tegra = platform_get_drvdata(pdev);
tegra_hv_ivc_unreserve(tegra->ivck);
tegra_hv_xhci_debug_debugfs_deinit(tegra);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id tegra_hv_xhci_debug_match[] = {
{ .compatible = "nvidia,tegra-hv-xhci-debug", },
{},
};
MODULE_DEVICE_TABLE(of, tegra_hv_xhci_debug_match);
#endif /* CONFIG_OF */
static struct platform_driver tegra_hv_xhci_debug_platform_driver = {
.probe = tegra_hv_xhci_debug_probe,
.remove = tegra_hv_xhci_debug_remove,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(tegra_hv_xhci_debug_match),
},
};
static int tegra_hv_xhci_debug_init(void)
{
return platform_driver_register(&tegra_hv_xhci_debug_platform_driver);
}
static void tegra_hv_xhci_debug_exit(void)
{
platform_driver_unregister(&tegra_hv_xhci_debug_platform_driver);
}
module_init(tegra_hv_xhci_debug_init);
module_exit(tegra_hv_xhci_debug_exit);
MODULE_AUTHOR("JC Kuo <jckuo@nvidia.com>");
MODULE_DESCRIPTION("Tegra Hyperisor XHCI Server Debug");
MODULE_LICENSE("GPL");