YaGL: passthrough device added
authorStanislav Vorobiov <s.vorobiov@samsung.com>
Mon, 10 Sep 2012 10:10:19 +0000 (14:10 +0400)
committerSeokYeon Hwang <syeon.hwang@samsung.com>
Wed, 9 Apr 2014 05:42:16 +0000 (14:42 +0900)
Conflicts:

arch/arm/configs/tizen_defconfig
drivers/gpu/Makefile

Change-Id: I76daea09e8d27338b763109dbfffa674f400e626

12 files changed:
drivers/gpu/Makefile
drivers/gpu/yagl/Kconfig [new file with mode: 0644]
drivers/gpu/yagl/Makefile [new file with mode: 0644]
drivers/gpu/yagl/debug.h [new file with mode: 0644]
drivers/gpu/yagl/main.c [new file with mode: 0644]
drivers/gpu/yagl/print.h [new file with mode: 0644]
drivers/gpu/yagl/yagl.h [new file with mode: 0644]
drivers/gpu/yagl/yagl_driver.c [new file with mode: 0644]
drivers/gpu/yagl/yagl_driver.h [new file with mode: 0644]
drivers/gpu/yagl/yagl_marshal.h [new file with mode: 0644]
drivers/gpu/yagl/yagl_version.h [new file with mode: 0644]
drivers/video/Kconfig

index d8a22c2a579d458c17f4fd5d7b57f5bdee13cf65..ca3a8bd4ceb5b7ca4b98d1107903f6d985df5145 100644 (file)
@@ -1,2 +1,2 @@
-obj-y                  += drm/ vga/
+obj-y                  += drm/ vga/ yagl/
 obj-$(CONFIG_TEGRA_HOST1X)     += host1x/
diff --git a/drivers/gpu/yagl/Kconfig b/drivers/gpu/yagl/Kconfig
new file mode 100644 (file)
index 0000000..efb06f4
--- /dev/null
@@ -0,0 +1,22 @@
+#
+# YaGL configuration
+#
+
+menuconfig YAGL
+       bool "YaGL passthrough driver"
+       depends on (DRM || FB)
+       default n
+       help
+         This module enables YaGL passthrough from emulated system
+         to hypervisor (for example, QEMU). Must be used together with fake
+         (hypervisor-aware) OpenGL ES libraries.
+
+if YAGL
+
+config YAGL_DEBUG
+       bool "YaGL calls debug messages"
+       default no
+       help
+         Enable YaGL debug messages.
+
+endif
diff --git a/drivers/gpu/yagl/Makefile b/drivers/gpu/yagl/Makefile
new file mode 100644 (file)
index 0000000..74e1836
--- /dev/null
@@ -0,0 +1,7 @@
+#
+# Makefile for the exynos video drivers.
+#
+
+ccflags-y := -Idrivers/gpu/yagl
+
+obj-$(CONFIG_YAGL) += main.o yagl_driver.o
diff --git a/drivers/gpu/yagl/debug.h b/drivers/gpu/yagl/debug.h
new file mode 100644 (file)
index 0000000..e96e3e1
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef _YAGL_DEBUG_H_
+#define _YAGL_DEBUG_H_
+
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include "yagl.h"
+
+#ifdef CONFIG_YAGL_DEBUG
+#   define dprintk(fmt, args...) printk(KERN_DEBUG YAGL_NAME "::%s: " fmt, __FUNCTION__, ## args)
+#else
+#   define dprintk(fmt, args...)
+#endif
+
+#endif
diff --git a/drivers/gpu/yagl/main.c b/drivers/gpu/yagl/main.c
new file mode 100644 (file)
index 0000000..dca9358
--- /dev/null
@@ -0,0 +1,31 @@
+#include <linux/module.h>
+#include <linux/init.h>
+#include "print.h"
+#include "yagl_driver.h"
+
+MODULE_AUTHOR("Stanislav Vorobiov");
+MODULE_LICENSE("Dual BSD/GPL");
+
+int yagl_init(void)
+{
+    int ret = yagl_driver_register();
+
+    if (ret != 0)
+    {
+        return ret;
+    }
+
+    print_info("module loaded\n");
+
+    return 0;
+}
+
+void yagl_cleanup(void)
+{
+    yagl_driver_unregister();
+
+    print_info("module unloaded\n");
+}
+
+module_init(yagl_init);
+module_exit(yagl_cleanup);
diff --git a/drivers/gpu/yagl/print.h b/drivers/gpu/yagl/print.h
new file mode 100644 (file)
index 0000000..bdca900
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef _YAGL_PRINT_H_
+#define _YAGL_PRINT_H_
+
+#include <linux/kernel.h>
+#include "yagl.h"
+
+#define print_info(fmt, args...) printk(KERN_INFO YAGL_NAME ": " fmt, ## args)
+
+#define print_error(fmt, args...) printk(KERN_ERR YAGL_NAME ": " fmt, ## args)
+
+#endif
diff --git a/drivers/gpu/yagl/yagl.h b/drivers/gpu/yagl/yagl.h
new file mode 100644 (file)
index 0000000..1544901
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef _YAGL_H_
+#define _YAGL_H_
+
+/*
+ * This is then module name.
+ */
+#define YAGL_NAME "yagl"
+
+#endif
diff --git a/drivers/gpu/yagl/yagl_driver.c b/drivers/gpu/yagl/yagl_driver.c
new file mode 100644 (file)
index 0000000..463cc82
--- /dev/null
@@ -0,0 +1,469 @@
+#include "yagl_driver.h"
+#include "yagl_marshal.h"
+#include "yagl_version.h"
+#include "yagl.h"
+#include "debug.h"
+#include "print.h"
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/pci.h>
+
+#define YAGL_REG_BUFFPTR 0
+#define YAGL_REG_TRIGGER 4
+#define YAGL_REGS_SIZE   8
+
+#define YAGL_MAX_USERS (PAGE_SIZE / YAGL_REGS_SIZE)
+
+#define YAGL_USER_PTR(regs, index) ((regs) + ((index) * YAGL_REGS_SIZE))
+
+#define PCI_VENDOR_ID_YAGL 0x19B1
+#define PCI_DEVICE_ID_YAGL 0x1010
+
+static struct pci_device_id yagl_pci_table[] __devinitdata =
+{
+    {
+        .vendor     = PCI_VENDOR_ID_YAGL,
+        .device     = PCI_DEVICE_ID_YAGL,
+        .subvendor  = PCI_ANY_ID,
+        .subdevice  = PCI_ANY_ID,
+    },
+    { 0 }
+};
+MODULE_DEVICE_TABLE(pci, yagl_pci_table);
+
+struct yagl_device
+{
+    /* PCI device we're managing */
+    struct pci_dev *pci_dev;
+
+    /* Misc device for accessing YaGL memory from user space */
+    struct miscdevice miscdev;
+
+    /* Physical address of YaGL registers. */
+    unsigned long regs_pa;
+
+    /* Memory area which is used for target <-> host communications */
+    void __iomem *regs;
+
+    /* 1 when user is active, 0 when slot can be used */
+    int user_map[YAGL_MAX_USERS];
+
+    /* Mutex used to serialize device operations */
+    struct mutex mutex;
+};
+
+struct yagl_file
+{
+    /* Owning device */
+    struct yagl_device *device;
+
+    /* Index in 'user_map', filled on 'open' */
+    int index;
+
+    /* Buffer used for marshalling, allocated in mmap */
+    u8 *buff;
+};
+
+static void yagl_user_activate(void __iomem *regs,
+                               int index,
+                               unsigned long buff_pa)
+{
+    writel(buff_pa, YAGL_USER_PTR(regs, index) + YAGL_REG_BUFFPTR);
+}
+
+static void yagl_user_deactivate(void __iomem *regs, int index)
+{
+    writel(0, YAGL_USER_PTR(regs, index) + YAGL_REG_BUFFPTR);
+}
+
+static int yagl_misc_open(struct inode *inode, struct file *file)
+{
+    int ret = 0;
+    struct yagl_device *device = container_of( file->private_data,
+                                               struct yagl_device,
+                                               miscdev );
+
+    struct yagl_file *yfile;
+    int i;
+
+    mutex_lock(&device->mutex);
+
+    if (file->f_mode & FMODE_EXEC) {
+        ret = -EPERM;
+        goto fail;
+    }
+
+    yfile = kzalloc(sizeof(*yfile), GFP_KERNEL);
+
+    if (!yfile) {
+        dprintk("%s: unable to allocate memory\n", device->miscdev.name);
+        ret = -ENOMEM;
+        goto fail;
+    }
+
+    yfile->device = device;
+    yfile->index = -1;
+
+    for (i = 0; i < YAGL_MAX_USERS; ++i) {
+        if (!device->user_map[i]) {
+            yfile->index = i;
+            device->user_map[i] = 1;
+            break;
+        }
+    }
+
+    if (yfile->index == -1) {
+        dprintk("%s: no free slots\n", device->miscdev.name);
+        ret = -ENOMEM;
+        goto fail;
+    }
+
+    file->private_data = yfile;
+
+    mutex_unlock(&device->mutex);
+
+    dprintk("%s: %d opened\n", device->miscdev.name, yfile->index);
+
+    return nonseekable_open(inode, file);
+
+fail:
+    mutex_unlock(&device->mutex);
+
+    return ret;
+}
+
+static int yagl_misc_release(struct inode *inode, struct file *file)
+{
+    struct yagl_file *yfile = file->private_data;
+    int index = yfile->index;
+
+    mutex_lock(&yfile->device->mutex);
+
+    yfile->device->user_map[yfile->index] = 0;
+    yfile->index = -1;
+
+    if (yfile->buff) {
+        yagl_user_deactivate(yfile->device->regs, index);
+
+        vfree(yfile->buff);
+        yfile->buff = NULL;
+
+        dprintk("%s: YaGL exited\n", yfile->device->miscdev.name);
+    }
+
+    mutex_unlock(&yfile->device->mutex);
+
+    dprintk("%s: %d closed\n", yfile->device->miscdev.name, index);
+
+    kfree(file->private_data);
+    file->private_data = NULL;
+
+    return 0;
+}
+
+static int yagl_misc_mmap(struct file *file, struct vm_area_struct *vma)
+{
+    struct yagl_file *yfile = file->private_data;
+    int num_pages = (vma->vm_end - vma->vm_start) / PAGE_SIZE;
+    int ret = 0;
+    u8 *buff = NULL;
+    pid_t process_id;
+    pid_t thread_id;
+
+    mutex_lock(&yfile->device->mutex);
+
+    if (num_pages != 1) {
+        dprintk("%s: mmap must be called for 1 page only\n",
+                yfile->device->miscdev.name);
+        ret = -EINVAL;
+        goto out;
+    }
+
+    if (vma->vm_pgoff == 0) {
+        /*
+         * First page is 'regs'.
+         */
+
+        vma->vm_flags |= VM_IO | VM_RESERVED;
+        vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+        ret = remap_pfn_range(vma,
+                              vma->vm_start,
+                              (yfile->device->regs_pa >> PAGE_SHIFT),
+                              num_pages,
+                              vma->vm_page_prot);
+
+        if (ret != 0) {
+            dprintk( "%s: unable to remap regs memory: %d\n",
+                     yfile->device->miscdev.name,
+                     ret );
+            goto out;
+        }
+    } else if (vma->vm_pgoff == 1) {
+        /*
+         * Second page is marshalling buffer.
+         */
+
+        if (yfile->buff) {
+            dprintk("%s: marshalling buffer already mapped\n",
+                    yfile->device->miscdev.name);
+            ret = -EIO;
+            goto out;
+        }
+
+        buff = vmalloc_user(PAGE_SIZE);
+
+        if (!buff) {
+            dprintk("%s: unable to alloc memory\n",
+                    yfile->device->miscdev.name);
+            ret = -ENOMEM;
+            goto out;
+        }
+
+        ret = remap_vmalloc_range(vma, buff, 0);
+
+        if (ret != 0) {
+            dprintk("%s: unable to remap marshalling memory: %d\n",
+                    yfile->device->miscdev.name,
+                    ret );
+            goto out;
+        }
+
+        vma->vm_flags |= VM_DONTEXPAND;
+
+        yfile->buff = buff;
+
+        process_id = task_tgid_vnr(current);
+        thread_id = task_pid_vnr(current);
+
+        yagl_marshal_put_uint32(&buff, YAGL_VERSION);
+        yagl_marshal_put_pid(&buff, process_id);
+        yagl_marshal_put_tid(&buff, thread_id);
+
+        yagl_user_activate(yfile->device->regs,
+                           yfile->index,
+                           vmalloc_to_pfn(yfile->buff) << PAGE_SHIFT);
+
+        buff = yfile->buff;
+
+        if (yagl_marshal_get_uint32(&buff) != 1)
+        {
+            buff = yfile->buff;
+            yfile->buff = NULL;
+            ret = -EIO;
+            print_error("%s: unable to init YaGL: probably version mismatch\n",
+                        yfile->device->miscdev.name);
+            goto out;
+        }
+
+        buff = yfile->buff;
+
+        yagl_marshal_put_uint32(&buff, yfile->index);
+
+        buff = NULL;
+
+        dprintk("%s: YaGL entered\n",
+                yfile->device->miscdev.name);
+    } else {
+        dprintk("%s: mmap must be called with page offset 0 or 1\n",
+                yfile->device->miscdev.name);
+        ret = -EINVAL;
+        goto out;
+    }
+
+    ret = 0;
+
+out:
+    vfree(buff);
+    mutex_unlock(&yfile->device->mutex);
+
+    return ret;
+}
+
+static long yagl_misc_ioctl(struct file* file, unsigned int cmd, unsigned long arg)
+{
+    int ret = 0;
+    unsigned int value = 0;
+
+    if (_IOC_TYPE(cmd) != YAGL_IOC_MAGIC) {
+        return -ENOTTY;
+    }
+
+    if (_IOC_DIR(cmd) & _IOC_READ) {
+        ret = !access_ok(VERIFY_WRITE, (void __user*)arg, _IOC_SIZE(cmd));
+    }
+
+    if (_IOC_DIR(cmd) & _IOC_WRITE) {
+        ret = ret || !access_ok(VERIFY_READ, (void __user*)arg, _IOC_SIZE(cmd));
+    }
+
+    if (ret != 0) {
+        return -EFAULT;
+    }
+
+    ret = 0;
+
+    switch (cmd) {
+    case YAGL_IOC_GET_VERSION:
+        value = YAGL_VERSION;
+        ret = put_user(value, (unsigned int __user*)arg);
+        break;
+    default:
+        ret = -ENOTTY;
+        break;
+    }
+
+    return ret;
+}
+
+static struct file_operations yagl_misc_fops =
+{
+    .owner          = THIS_MODULE,
+    .open           = yagl_misc_open,
+    .mmap           = yagl_misc_mmap,
+    .release        = yagl_misc_release,
+    .unlocked_ioctl = yagl_misc_ioctl,
+};
+
+static int __devinit yagl_driver_probe(struct pci_dev *pci_dev,
+                                       const struct pci_device_id *pci_id)
+{
+    int ret = 0;
+    struct yagl_device *device = NULL;
+    u32 mem_size = 0;
+
+    dprintk("probing PCI device \"%s\"\n", dev_name(&pci_dev->dev));
+
+    device = kzalloc(sizeof(*device), GFP_KERNEL);
+
+    if (!device) {
+        ret = -ENOMEM;
+        goto fail;
+    }
+
+    ret = pci_enable_device(pci_dev);
+
+    if (ret != 0) {
+        dprintk("%s: unable to enable PCI device\n", dev_name(&pci_dev->dev));
+
+        goto fail;
+    }
+
+    device->pci_dev = pci_dev;
+
+    pci_set_master(pci_dev);
+
+    if (!pci_resource_start(pci_dev, 0)) {
+        dprintk("%s: bad PCI resource\n", dev_name(&pci_dev->dev));
+        ret = -ENXIO;
+        goto fail;
+    }
+
+    mem_size = pci_resource_len(pci_dev, 0);
+
+    if (mem_size != PAGE_SIZE) {
+        dprintk("%s: mem size must be PAGE_SIZE\n", dev_name(&pci_dev->dev));
+        ret = -ENXIO;
+        goto fail;
+    }
+
+    if (!request_mem_region(pci_resource_start(pci_dev, 0),
+                            PAGE_SIZE,
+                            dev_name(&pci_dev->dev))) {
+        dprintk("%s: mem size must be PAGE_SIZE\n", dev_name(&pci_dev->dev));
+        ret = -EBUSY;
+        goto fail;
+    }
+
+    device->regs_pa = pci_resource_start(pci_dev, 0);
+
+    device->regs = ioremap(device->regs_pa, mem_size);
+
+    if (!device->regs) {
+        ret = -ENXIO;
+        goto fail;
+    }
+
+    mutex_init(&device->mutex);
+
+    device->miscdev.minor = MISC_DYNAMIC_MINOR;
+    device->miscdev.name = YAGL_NAME;
+    device->miscdev.fops = &yagl_misc_fops;
+
+    ret = misc_register(&device->miscdev);
+
+    if (ret != 0) {
+        dprintk("%s: unable to register misc device\n", dev_name(&pci_dev->dev));
+
+        goto fail;
+    }
+
+    pci_set_drvdata(pci_dev, device);
+
+    print_info("%s: device added\n", dev_name(&pci_dev->dev));
+
+    return 0;
+
+fail:
+    if (device) {
+        if (device->regs) {
+            iounmap(device->regs);
+        }
+        if (device->regs_pa) {
+            release_mem_region(device->regs_pa, mem_size);
+        }
+        if (device->pci_dev) {
+            pci_disable_device(device->pci_dev);
+        }
+        kfree(device);
+    }
+
+    return ret;
+}
+
+static void __devinit yagl_driver_remove(struct pci_dev *pci_dev)
+{
+    struct yagl_device* device;
+
+    dprintk("removing driver from \"%s\"\n", dev_name(&pci_dev->dev));
+
+    device = pci_get_drvdata(pci_dev);
+
+    if (device != NULL) {
+        misc_deregister(&device->miscdev);
+
+        pci_set_drvdata(pci_dev, NULL);
+
+        iounmap(device->regs);
+        release_mem_region(device->regs_pa, PAGE_SIZE);
+        pci_disable_device(device->pci_dev);
+        kfree(device);
+    }
+}
+
+static struct pci_driver yagl_driver =
+{
+    .name       = YAGL_NAME,
+    .id_table   = yagl_pci_table,
+    .probe      = yagl_driver_probe,
+    .remove     = __devexit_p(yagl_driver_remove),
+};
+
+int yagl_driver_register(void)
+{
+    return pci_register_driver(&yagl_driver);
+}
+
+void yagl_driver_unregister(void)
+{
+    pci_unregister_driver(&yagl_driver);
+}
diff --git a/drivers/gpu/yagl/yagl_driver.h b/drivers/gpu/yagl/yagl_driver.h
new file mode 100644 (file)
index 0000000..51cb95f
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef _YAGL_DRIVER_H_
+#define _YAGL_DRIVER_H_
+
+#include <linux/types.h>
+
+int yagl_driver_register(void);
+
+void yagl_driver_unregister(void);
+
+#endif
diff --git a/drivers/gpu/yagl/yagl_marshal.h b/drivers/gpu/yagl/yagl_marshal.h
new file mode 100644 (file)
index 0000000..368a2bd
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef _YAGL_MARSHAL_H_
+#define _YAGL_MARSHAL_H_
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/byteorder.h>
+
+/*
+ * All marshalling/unmarshalling must be done with 8-byte alignment,
+ * since this is the maximum alignment possible. This way we can
+ * just do assignments without "memcpy" calls and can be sure that
+ * the code won't fail on architectures that don't support unaligned
+ * memory access.
+ */
+
+static __inline void yagl_marshal_put_uint32(u8** buff, u32 value)
+{
+    *(u32*)(*buff) = cpu_to_le32(value);
+    *buff += 8;
+}
+
+static __inline u32 yagl_marshal_get_uint32(u8** buff)
+{
+    u32 tmp = le32_to_cpu(*(u32*)*buff);
+    *buff += 8;
+    return tmp;
+}
+
+#define yagl_marshal_put_pid(buff, value) yagl_marshal_put_uint32(buff, value)
+#define yagl_marshal_put_tid(buff, value) yagl_marshal_put_uint32(buff, value)
+
+#endif
diff --git a/drivers/gpu/yagl/yagl_version.h b/drivers/gpu/yagl/yagl_version.h
new file mode 100644 (file)
index 0000000..b4c6125
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef _YAGL_VERSION_H_
+#define _YAGL_VERSION_H_
+
+#include <linux/ioctl.h>
+
+/*
+ * Version number.
+ */
+#define YAGL_VERSION 2
+
+/*
+ * Device control codes magic.
+ */
+#define YAGL_IOC_MAGIC 'Y'
+
+/*
+ * Get version number.
+ */
+#define YAGL_IOC_GET_VERSION _IOR(YAGL_IOC_MAGIC, 0, unsigned int)
+
+#endif
index 84b685f7ab6e0a3389c534b56e8f8187619e1e84..87b7f4171b3e1a21fb4fc5f240d3d5c452deee20 100644 (file)
@@ -23,6 +23,8 @@ source "drivers/gpu/drm/Kconfig"
 
 source "drivers/gpu/host1x/Kconfig"
 
+source "drivers/gpu/yagl/Kconfig"
+
 config VGASTATE
        tristate
        default n