/* * Copyright 2017 Red Hat Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that 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. * * Authors: Jérôme Glisse */ #include #include #include #include #include #include #include "compote.h" #include #include #include "nouveau_ttm.h" #include "nouveau_usif.h" #include "nouveau_abi16.h" static DEFINE_SPINLOCK(compote_minor_lock); static DECLARE_BITMAP(compote_minor, 256); static struct class *compote_class = NULL; static dev_t _compote_devt; static u64 compote_name(struct nouveau_drm *nvdrm) { struct pci_dev *pdev = nvdrm->dev->pdev; u64 name = (u64)pci_domain_nr(pdev->bus) << 32; name |= pdev->bus->number << 16; name |= PCI_SLOT(pdev->devfn) << 8; return name | PCI_FUNC(pdev->devfn); } static void compote_client_fini(struct nouveau_cli *nvclient) { if (nvclient->vm) nvkm_vm_ref(NULL, &nvclient->vm, NULL); usif_client_fini(nvclient); nvif_device_fini(&nvclient->device); nvif_client_fini(&nvclient->base); } static int compote_client_init(struct nouveau_cli *nvclient, struct nouveau_drm *nvdrm, const char *sname) { u64 device = compote_name(nvdrm); int ret; snprintf(nvclient->name, sizeof(nvclient->name), "%s", sname); nvclient->dev = nvdrm->dev; mutex_init(&nvclient->mutex); usif_client_init(nvclient); ret = nvif_client_init(&nvdrm->client.base, nvclient->name, device, &nvclient->base); if (ret) { printk(KERN_ERR "nvif_client_init(): %d\n", ret); goto error; } ret = nvif_device_init(&nvclient->base.object, 0, NV_DEVICE, &(struct nv_device_v0) { .device = ~0, }, sizeof(struct nv_device_v0), &nvclient->device); if (ret) { printk(KERN_ERR "nvif_device_init(): %d\n", ret); goto error; } return 0; error: compote_client_fini(nvclient); return ret; } static int compote_open(struct inode *inode, struct file *file) { struct compote_file *cfile = kzalloc(sizeof(*cfile), GFP_KERNEL); char name[TASK_COMM_LEN + 8], tmpname[TASK_COMM_LEN]; struct compote_device *cdevice; int ret; if(cfile == NULL) return -ENOMEM; /* need to bring up power immediately if opening device */ cdevice = container_of(inode->i_cdev, struct compote_device, cdev); ret = pm_runtime_get_sync(cdevice->nvdrm->dev->dev); if (ret < 0 && ret != -EACCES) { kfree(cfile); return ret; } get_task_comm(tmpname, current); snprintf(name, sizeof(name), "%s[%d]", tmpname, pid_nr(get_pid(task_pid(current)))); INIT_LIST_HEAD(&cfile->channels); init_rwsem(&cfile->rwsem); cfile->cdevice = cdevice; cfile->file = file; ret = compote_client_init(&cfile->nvclient, cdevice->nvdrm, name); if (ret) goto error; /* * Too many place in nouveau that rely on 40bits so stick with that for * now. */ ret = nvkm_vm_new(nvxx_device(&cdevice->nvdrm->client.device), 0, (1ULL << 40), 0x1000, NULL, &cfile->nvclient.vm); if (ret) goto error_vm; nvxx_client(&cfile->nvclient.base)->vm = cfile->nvclient.vm; /* share address_space across all char-devs of a single device */ file->f_mapping = cdevice->nvdrm->dev->anon_inode->i_mapping; pm_runtime_mark_last_busy(cdevice->nvdrm->dev->dev); pm_runtime_put_autosuspend(cdevice->nvdrm->dev->dev); cfile->nvclient.base.super = false; file->private_data = cfile; return 0; error_vm: compote_client_fini(&cfile->nvclient); error: kfree(cfile); pm_runtime_mark_last_busy(cdevice->nvdrm->dev->dev); pm_runtime_put_autosuspend(cdevice->nvdrm->dev->dev); return ret; } static int compote_close(struct inode *inode, struct file *file) { struct compote_file *cfile = file->private_data; struct compote_channel *channel, *tmp; struct compote_device *cdevice; if(cfile == NULL) return 0; pm_runtime_get_sync(cfile->cdevice->nvdrm->dev->dev); list_for_each_entry_safe (channel, tmp, &cfile->channels, list) { compote_channel_unref(channel); } compote_client_fini(&cfile->nvclient); cdevice = cfile->cdevice; kfree(cfile); pm_runtime_mark_last_busy(cdevice->nvdrm->dev->dev); pm_runtime_put_autosuspend(cdevice->nvdrm->dev->dev); return 0; } static ssize_t compote_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { return -EINVAL; } static int compote_mmap(struct file *file, struct vm_area_struct *vma) { struct compote_file *cfile = file->private_data; if (cfile == NULL) return -EINVAL; return compote_mo_mmap(cfile, vma, file); } static ssize_t compote_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { return -EINVAL; } static long compote_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct compote_file *cfile = file->private_data; void __user *uarg = (void __user *)arg; long ret; if (!cfile) return -EINVAL; ret = pm_runtime_get_sync(cfile->cdevice->nvdrm->dev->dev); if (WARN_ON(ret < 0 && ret != -EACCES)) return ret; switch (cmd) { case COMPOTE_IOCTL_MEM_ALLOC: ret = compote_ioctl_mem_alloc(cfile, uarg); break; case COMPOTE_IOCTL_MEM_FREE: ret = compote_ioctl_mem_free(cfile, uarg); break; case COMPOTE_IOCTL_CHAN_ALLOC: ret = compote_ioctl_channel_alloc(cfile, uarg); break; case COMPOTE_IOCTL_CHAN_FREE: ret = compote_ioctl_channel_free(cfile, uarg); break; default: ret = -EINVAL; break; } pm_runtime_mark_last_busy(cfile->cdevice->nvdrm->dev->dev); pm_runtime_put_autosuspend(cfile->cdevice->nvdrm->dev->dev); return ret; } static const struct file_operations compote_fops = { .open = compote_open, .read = compote_read, .mmap = compote_mmap, .write = compote_write, .release = compote_close, .unlocked_ioctl = compote_ioctl, .owner = THIS_MODULE, }; static bool compote_minor_get(int *minor) { spin_lock(&compote_minor_lock); *minor = bitmap_find_next_zero_area(compote_minor, 256, 0, 1, 0); if ((*minor) < 256) { bitmap_set(compote_minor, *minor, 1); spin_unlock(&compote_minor_lock); return true; } spin_unlock(&compote_minor_lock); return false; } static void compote_minor_put(int minor) { spin_lock(&compote_minor_lock); bitmap_clear(compote_minor, minor, 1); spin_unlock(&compote_minor_lock); } void compote_device_fini(void *compote_device) { struct compote_device *cdevice = compote_device; if (cdevice == NULL) return; if (cdevice->device) { dev_t dev = MKDEV(MAJOR(_compote_devt), cdevice->minor); device_destroy(compote_class, dev); cdev_del(&cdevice->cdev); } compote_minor_put(cdevice->minor); kfree(cdevice); } void *compote_device_init(struct nouveau_drm *nvdrm) { const struct nv_device_info_v0 *devinfo = &nvdrm->client.device.info; struct compote_device *cdevice; dev_t dev; int ret; /* Only support maxwell and pascal */ switch (devinfo->family) { case NV_DEVICE_INFO_V0_MAXWELL: case NV_DEVICE_INFO_V0_PASCAL: break; default: return NULL; } cdevice = kzalloc(sizeof(*cdevice), GFP_KERNEL); if (cdevice == NULL) return NULL; cdevice->chipset = devinfo->chipset; cdevice->nvdrm = nvdrm; if (!compote_minor_get(&cdevice->minor)) { kfree(cdevice); return NULL; } dev = MKDEV(MAJOR(_compote_devt), cdevice->minor); cdev_init(&cdevice->cdev, &compote_fops); cdevice->cdev.owner = THIS_MODULE; ret = cdev_add(&cdevice->cdev, dev, 1); if (ret) { kfree(cdevice); return NULL; } cdevice->device = device_create(compote_class, nvdrm->dev->dev, dev, NULL, "%s%d", COMPOTE_DEVICE_NAME, cdevice->minor); if (cdevice->device == NULL) { cdev_del(&cdevice->cdev); compote_device_fini(cdevice); return NULL; } printk(KERN_INFO "%s%d for 0x%02x chipset\n", COMPOTE_DEVICE_NAME, cdevice->minor, cdevice->chipset); return cdevice; } void compote_exit(void) { if (!compote_class) return; class_destroy(compote_class); unregister_chrdev_region(_compote_devt, 256); } int compote_init(void) { int ret; ret = alloc_chrdev_region(&_compote_devt, 0, 256, COMPOTE_DEVICE_NAME); if (ret) return ret; compote_class = class_create(THIS_MODULE, COMPOTE_DEVICE_NAME); if (IS_ERR(compote_class)) { int ret = PTR_ERR(compote_class); unregister_chrdev_region(_compote_devt, 256); compote_class = NULL; return ret; } return 0; }