diff --git a/drivers/platform/tegra/Makefile b/drivers/platform/tegra/Makefile index bf259bdd..ac82872f 100644 --- a/drivers/platform/tegra/Makefile +++ b/drivers/platform/tegra/Makefile @@ -21,3 +21,4 @@ endif obj-m += aon/ obj-m += tegra-uss-io-proxy.o obj-m += nvadsp/ +obj-m += tegra-hv-xhci-debug.o diff --git a/drivers/platform/tegra/tegra-hv-xhci-debug.c b/drivers/platform/tegra/tegra-hv-xhci-debug.c new file mode 100644 index 00000000..e269199f --- /dev/null +++ b/drivers/platform/tegra/tegra-hv-xhci-debug.c @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-FileCopyrightText: Copyright (c) 2016-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +/* + * tegra-hv-xhci-debug: Tegra Hypervisor XHCI server debug + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "tegra_hv_xhci_debug" + +#include +#include +#include + +/* frame format is + * 0000: + * 0004: + * 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; + + 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 "); +MODULE_DESCRIPTION("Tegra Hyperisor XHCI Server Debug"); +MODULE_LICENSE("GPL");