/* * 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 #include #include #include #include #include #include #include #include #include #include #include #define DRV_NAME "tegra_hv_xhci_debug" #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; 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 "); MODULE_DESCRIPTION("Tegra Hyperisor XHCI Server Debug"); MODULE_LICENSE("GPL");