/* * IVC character device driver * * Copyright (C) 2014-2018, NVIDIA CORPORATION. All rights reserved. * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. * */ #include #include #include #include #include #include #include #include #include #include #include #include "tegra_hv.h" #define ERR(...) pr_err("ivc: " __VA_ARGS__) #define DBG(...) pr_debug("ivc: " __VA_ARGS__) struct ivc_dev { int minor; dev_t dev; struct cdev cdev; struct device *device; char name[32]; /* channel configuration */ struct tegra_hv_ivc_cookie *ivck; const struct tegra_hv_queue_data *qd; /* File mode */ wait_queue_head_t wq; /* * Lock for synchronizing access to the IVC channel between the threaded * IRQ handler's notification processing and file ops. */ struct mutex file_lock; }; static dev_t ivc_dev; static const struct ivc_info_page *info; static irqreturn_t ivc_dev_handler(int irq, void *data) { struct ivc_dev *ivc = data; BUG_ON(!ivc->ivck); mutex_lock(&ivc->file_lock); tegra_ivc_channel_notified(tegra_hv_ivc_convert_cookie(ivc->ivck)); mutex_unlock(&ivc->file_lock); /* simple implementation, just kick all waiters */ wake_up_interruptible_all(&ivc->wq); return IRQ_HANDLED; } static irqreturn_t ivc_threaded_irq_handler(int irq, void *dev_id) { /* * Virtual IRQs are known to be edge-triggered, so no action is needed * to acknowledge them. */ return IRQ_WAKE_THREAD; } static int ivc_dev_open(struct inode *inode, struct file *filp) { struct cdev *cdev = inode->i_cdev; struct ivc_dev *ivc = container_of(cdev, struct ivc_dev, cdev); int ret; struct tegra_hv_ivc_cookie *ivck; struct ivc *ivcq; /* * If we can reserve the corresponding IVC device successfully, then * we have exclusive access to the ivc device. */ ivck = tegra_hv_ivc_reserve(NULL, ivc->minor, NULL); if (IS_ERR(ivck)) return PTR_ERR(ivck); ivc->ivck = ivck; ivcq = tegra_hv_ivc_convert_cookie(ivck); mutex_lock(&ivc->file_lock); tegra_ivc_channel_reset(ivcq); mutex_unlock(&ivc->file_lock); /* request our irq */ ret = devm_request_threaded_irq(ivc->device, ivck->irq, ivc_threaded_irq_handler, ivc_dev_handler, 0, dev_name(ivc->device), ivc); if (ret < 0) { dev_err(ivc->device, "Failed to request irq %d\n", ivck->irq); ivc->ivck = NULL; tegra_hv_ivc_unreserve(ivck); return ret; } /* all done */ filp->private_data = ivc; return 0; } static int ivc_dev_release(struct inode *inode, struct file *filp) { struct ivc_dev *ivc = filp->private_data; struct tegra_hv_ivc_cookie *ivck; filp->private_data = NULL; BUG_ON(!ivc); ivck = ivc->ivck; devm_free_irq(ivc->device, ivck->irq, ivc); ivc->ivck = NULL; /* * Unreserve after clearing ivck; we no longer have exclusive * access at this point. */ tegra_hv_ivc_unreserve(ivck); return 0; } static ssize_t ivc_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct ivc_dev *ivcd = filp->private_data; struct ivc *ivc; int left = count, ret = 0, chunk; BUG_ON(!ivcd); ivc = tegra_hv_ivc_convert_cookie(ivcd->ivck); if (!tegra_ivc_can_read(ivc)) { if (filp->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_interruptible(ivcd->wq, tegra_ivc_can_read(ivc)); if (ret) return ret; } while (left > 0 && tegra_ivc_can_read(ivc)) { chunk = ivcd->qd->frame_size; if (chunk > left) chunk = left; mutex_lock(&ivcd->file_lock); ret = tegra_ivc_read_user(ivc, buf, chunk); mutex_unlock(&ivcd->file_lock); if (ret < 0) break; buf += chunk; left -= chunk; } if (left >= count) return ret; return count - left; } static ssize_t ivc_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) { struct ivc_dev *ivcd = filp->private_data; struct ivc *ivc; ssize_t done; size_t left, chunk; int ret = 0; BUG_ON(!ivcd); ivc = tegra_hv_ivc_convert_cookie(ivcd->ivck); done = 0; while (done < count) { left = count - done; if (left < ivcd->qd->frame_size) chunk = left; else chunk = ivcd->qd->frame_size; /* is queue full? */ if (!tegra_ivc_can_write(ivc)) { /* check non-blocking mode */ if (filp->f_flags & O_NONBLOCK) { ret = -EAGAIN; break; } ret = wait_event_interruptible(ivcd->wq, tegra_ivc_can_write(ivc)); if (ret) break; } mutex_lock(&ivcd->file_lock); ret = tegra_ivc_write_user(ivc, buf, chunk); mutex_unlock(&ivcd->file_lock); if (ret < 0) break; buf += chunk; done += chunk; *pos += chunk; } if (done == 0) return ret; return done; } static unsigned int ivc_dev_poll(struct file *filp, poll_table *wait) { struct ivc_dev *ivcd = filp->private_data; struct ivc *ivc; int mask = 0; BUG_ON(!ivcd); ivc = tegra_hv_ivc_convert_cookie(ivcd->ivck); poll_wait(filp, &ivcd->wq, wait); if (tegra_ivc_can_read(ivc)) mask = POLLIN | POLLRDNORM; if (tegra_ivc_can_write(ivc)) mask |= POLLOUT | POLLWRNORM; /* no exceptions */ return mask; } static const struct file_operations ivc_fops = { .owner = THIS_MODULE, .open = ivc_dev_open, .release = ivc_dev_release, .llseek = noop_llseek, .read = ivc_dev_read, .write = ivc_dev_write, .poll = ivc_dev_poll, }; static ssize_t id_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ivc_dev *ivc = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", ivc->qd->id); } static ssize_t frame_size_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ivc_dev *ivc = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", ivc->qd->frame_size); } static ssize_t nframes_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ivc_dev *ivc = dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\n", ivc->qd->nframes); } static ssize_t reserved_show(struct device *dev, struct device_attribute *attr, char *buf) { struct tegra_hv_ivc_cookie *ivck; struct ivc_dev *ivc = dev_get_drvdata(dev); int reserved; ivck = tegra_hv_ivc_reserve(NULL, ivc->minor, NULL); if (IS_ERR(ivck)) reserved = 1; else { tegra_hv_ivc_unreserve(ivck); reserved = 0; } return snprintf(buf, PAGE_SIZE, "%d\n", reserved); } static ssize_t peer_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ivc_dev *ivc = dev_get_drvdata(dev); int guestid = tegra_hv_get_vmid(); return snprintf(buf, PAGE_SIZE, "%d\n", ivc->qd->peers[0] == guestid ? ivc->qd->peers[1] : ivc->qd->peers[0]); } static DEVICE_ATTR_RO(id); static DEVICE_ATTR_RO(frame_size); static DEVICE_ATTR_RO(nframes); static DEVICE_ATTR_RO(reserved); static DEVICE_ATTR_RO(peer); struct attribute *ivc_attrs[] = { &dev_attr_id.attr, &dev_attr_frame_size.attr, &dev_attr_nframes.attr, &dev_attr_peer.attr, &dev_attr_reserved.attr, NULL, }; ATTRIBUTE_GROUPS(ivc); static dev_t ivc_dev; static uint32_t max_qid; static struct ivc_dev *ivc_dev_array; static struct class *ivc_class; static int __init add_ivc(int i) { const struct tegra_hv_queue_data *qd = &ivc_info_queue_array(info)[i]; struct ivc_dev *ivc = &ivc_dev_array[i]; int ret; ivc->minor = qd->id; ivc->dev = MKDEV(MAJOR(ivc_dev), qd->id); ivc->qd = qd; cdev_init(&ivc->cdev, &ivc_fops); snprintf(ivc->name, sizeof(ivc->name) - 1, "ivc%d", qd->id); ret = cdev_add(&ivc->cdev, ivc->dev, 1); if (ret != 0) { ERR("cdev_add() failed\n"); return ret; } mutex_init(&ivc->file_lock); init_waitqueue_head(&ivc->wq); /* parent is this hvd dev */ ivc->device = device_create(ivc_class, NULL, ivc->dev, ivc, ivc->name); if (IS_ERR(ivc->device)) { ERR("device_create() failed for %s\n", ivc->name); return PTR_ERR(ivc->device); } /* point to ivc */ dev_set_drvdata(ivc->device, ivc); return 0; } static int __init setup_ivc(void) { uint32_t i; int result; max_qid = 0; for (i = 0; i < info->nr_queues; i++) { const struct tegra_hv_queue_data *qd = &ivc_info_queue_array(info)[i]; if (qd->id > max_qid) max_qid = qd->id; } /* allocate the whole chardev range */ result = alloc_chrdev_region(&ivc_dev, 0, max_qid, "ivc"); if (result < 0) { ERR("alloc_chrdev_region() failed\n"); return result; } ivc_class = class_create(THIS_MODULE, "ivc"); if (IS_ERR(ivc_class)) { ERR("failed to create ivc class: %ld\n", PTR_ERR(ivc_class)); return PTR_ERR(ivc_class); } ivc_class->dev_groups = ivc_groups; ivc_dev_array = kcalloc(info->nr_queues, sizeof(*ivc_dev_array), GFP_KERNEL); if (!ivc_dev_array) { ERR("failed to allocate ivc_dev_array"); return -ENOMEM; } /* * Make a second pass through the queues to instantiate the char devs * corresponding to existent queues. */ for (i = 0; i < info->nr_queues; i++) { result = add_ivc(i); if (result != 0) return result; } return 0; } static void __init cleanup_ivc(void) { uint32_t i; if (ivc_dev_array) { for (i = 0; i < info->nr_queues; i++) { struct ivc_dev *ivc = &ivc_dev_array[i]; if (ivc->device) { cdev_del(&ivc->cdev); device_del(ivc->device); } } kfree(ivc_dev_array); ivc_dev_array = NULL; } if (!IS_ERR_OR_NULL(ivc_class)) { class_destroy(ivc_class); ivc_class = NULL; } if (ivc_dev) { unregister_chrdev_region(ivc_dev, max_qid); ivc_dev = 0; } } static int __init ivc_init(void) { int result; info = tegra_hv_get_ivc_info(); if (IS_ERR(info)) return -ENODEV; result = setup_ivc(); if (result != 0) cleanup_ivc(); return result; } module_init(ivc_init);