Den 02.09.2016 10:22, skrev David Herrmann:
> The SimpleDRM driver binds to simple-framebuffer devices and provides a
> DRM/KMS API. It provides only a single CRTC+encoder+connector combination
> plus one initial mode.
>
> Userspace can create dumb-buffers which can be blit into the real
> framebuffer similar to UDL. No access to the real framebuffer is allowed
> (compared to earlier version of this driver) to avoid security issues.
> Furthermore, this way we can support arbitrary modes as long as we have a
> conversion-helper.
>
> Signed-off-by: David Herrmann <dh.herrmann at gmail.com>
> ---

[...]

> diff --git a/drivers/gpu/drm/simpledrm/simpledrm_drv.c 
> b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> new file mode 100644
> index 0000000..d569120
> --- /dev/null
> +++ b/drivers/gpu/drm/simpledrm/simpledrm_drv.c
> @@ -0,0 +1,464 @@
> +/*
> + * Copyright (C) 2012-2016 Red Hat, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU Lesser General Public License as published by 
> the
> + * Free Software Foundation; either version 2.1 of the License, or (at your
> + * option) any later version.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +#include <drm/drmP.h>
> +#include <linux/atomic.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_data/simplefb.h>
> +#include <linux/string.h>
> +#include "simpledrm.h"
> +
> +static struct drm_driver sdrm_drm_driver;
> +static DEFINE_MUTEX(sdrm_lock);
> +
> +static int sdrm_hw_identify(struct platform_device *pdev,
> +                         struct simplefb_platform_data *modep,
> +                         struct simplefb_format *formatp,
> +                         struct resource **memp,
> +                         u32 *bppp)
> +{
> +     static const struct simplefb_format valid_formats[] = SIMPLEFB_FORMATS;
> +     struct simplefb_platform_data pm = {}, *mode = pdev->dev.platform_data;
> +     struct device_node *np = pdev->dev.of_node;
> +     const struct simplefb_format *format = NULL;
> +     struct resource *mem;
> +     unsigned int depth;
> +     int r, bpp;
> +     size_t i;
> +
> +     if (!mode) {
> +             if (!np)
> +                     return -ENODEV;
> +
> +             mode = &pm;
> +
> +             r = of_property_read_u32(np, "width", &mode->width);
> +             if (r >= 0)
> +                     r = of_property_read_u32(np, "height", &mode->height);
> +             if (r >= 0)
> +                     r = of_property_read_u32(np, "stride", &mode->stride);
> +             if (r >= 0)
> +                     r = of_property_read_string(np, "format",
> +                                                 &mode->format);
> +             if (r < 0)
> +                     return r;
> +     }
> +
> +     mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +     if (!mem)
> +             return -ENODEV;
> +
> +     for (i = 0; i < ARRAY_SIZE(valid_formats); ++i) {
> +             if (!strcmp(mode->format, valid_formats[i].name)) {
> +                     format = &valid_formats[i];
> +                     break;
> +             }
> +     }
> +
> +     if (!format)
> +             return -ENODEV;
> +
> +     switch (format->fourcc) {
> +     case DRM_FORMAT_RGB565:
> +     case DRM_FORMAT_XRGB1555:
> +     case DRM_FORMAT_ARGB1555:
> +     case DRM_FORMAT_RGB888:
> +     case DRM_FORMAT_XRGB8888:
> +     case DRM_FORMAT_ARGB8888:
> +     case DRM_FORMAT_ABGR8888:
> +     case DRM_FORMAT_XRGB2101010:
> +     case DRM_FORMAT_ARGB2101010:
> +             /*
> +              * You must adjust sdrm_put() whenever you add a new format
> +              * here, otherwise, blitting operations will not work.
> +              * Furthermore, include/linux/platform_data/simplefb.h needs
> +              * to be adjusted so the platform-device actually allows this
> +              * format.
> +              */
> +             break;
> +     default:
> +             return -ENODEV;
> +     }
> +
> +     drm_fb_get_bpp_depth(format->fourcc, &depth, &bpp);
> +     if (!bpp)
> +             return -ENODEV;
> +     if (resource_size(mem) < mode->stride * mode->height)
> +             return -ENODEV;
> +     if ((bpp + 7) / 8 * mode->width > mode->stride)
> +             return -ENODEV;
> +
> +     *modep = *mode;
> +     *formatp = *format;
> +     *memp = mem;
> +     *bppp = bpp;
> +     return 0;
> +}
> +
> +static struct sdrm_hw *sdrm_hw_new(const struct simplefb_platform_data *mode,
> +                                const struct simplefb_format *format,
> +                                const struct resource *mem,
> +                                u32 bpp)
> +{
> +     struct sdrm_hw *hw;
> +
> +     hw = kzalloc(sizeof(*hw), GFP_KERNEL);
> +     if (!hw)
> +             return ERR_PTR(-ENOMEM);
> +
> +     mutex_init(&hw->lock);
> +     hw->width = mode->width;
> +     hw->height = mode->height;
> +     hw->stride = mode->stride;
> +     hw->bpp = bpp;
> +     hw->format = format->fourcc;
> +     hw->base = mem->start;
> +     hw->size = resource_size(mem);
> +
> +     return hw;
> +}
> +
> +static struct sdrm_hw *sdrm_hw_free(struct sdrm_hw *hw)
> +{
> +     if (!hw)
> +             return NULL;
> +
> +     WARN_ON(hw->map);
> +     mutex_destroy(&hw->lock);
> +     kfree(hw);
> +
> +     return NULL;
> +}
> +
> +static int sdrm_hw_bind(struct sdrm_hw *hw)
> +{
> +     mutex_lock(&hw->lock);
> +     if (!hw->map)
> +             hw->map = ioremap_wc(hw->base, hw->size);
> +     mutex_unlock(&hw->lock);
> +
> +     return hw->map ? 0 : -EIO;
> +}
> +
> +static void sdrm_hw_unbind(struct sdrm_hw *hw)
> +{
> +     if (!hw)
> +             return;
> +
> +     mutex_lock(&hw->lock);
> +     if (hw->map) {
> +             iounmap(hw->map);
> +             hw->map = NULL;
> +     }
> +     mutex_unlock(&hw->lock);
> +}
> +
> +static struct sdrm_device *sdrm_device_free(struct sdrm_device *sdrm)
> +{
> +     if (!sdrm)
> +             return NULL;
> +
> +     WARN_ON(atomic_read(&sdrm->n_used) != INT_MIN);
> +     sdrm->hw = sdrm_hw_free(sdrm->hw);
> +     drm_dev_unref(sdrm->ddev);
> +     kfree(sdrm);
> +
> +     return NULL;
> +}
> +
> +static struct sdrm_device *sdrm_device_new(struct platform_device *pdev,
> +                                        struct sdrm_hw *hw)
> +{
> +     struct sdrm_device *sdrm;
> +     int r;
> +
> +     sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL);
> +     if (!sdrm)
> +             return ERR_PTR(-ENOMEM);
> +
> +     atomic_set(&sdrm->n_used, INT_MIN);
> +
> +     sdrm->ddev = drm_dev_alloc(&sdrm_drm_driver, &pdev->dev);
> +     if (!sdrm->ddev) {
> +             r = -ENOMEM;
> +             goto error;
> +     }
> +
> +     sdrm->ddev->dev_private = sdrm;
> +     sdrm->hw = hw;
> +
> +     return sdrm;
> +
> +error:
> +     sdrm_device_free(sdrm);
> +     return ERR_PTR(r);
> +}
> +
> +static void sdrm_device_unbind(struct sdrm_device *sdrm)
> +{
> +     if (sdrm) {
> +             sdrm_kms_unbind(sdrm);
> +             sdrm_hw_unbind(sdrm->hw);
> +             sdrm_of_unbind(sdrm);
> +     }
> +}
> +
> +static int sdrm_device_bind(struct sdrm_device *sdrm)
> +{
> +     int r;
> +
> +     r = sdrm_of_bind(sdrm);
> +     if (r < 0)
> +             goto error;
> +
> +     r = sdrm_hw_bind(sdrm->hw);
> +     if (r < 0)
> +             goto error;
> +
> +     r = sdrm_kms_bind(sdrm);
> +     if (r < 0)
> +             goto error;
> +
> +     return 0;
> +
> +error:
> +     sdrm_device_unbind(sdrm);
> +     return r;
> +}
> +
> +static int sdrm_device_acquire(struct sdrm_device *sdrm)
> +{
> +     return (sdrm && atomic_inc_unless_negative(&sdrm->n_used))
> +             ? 0 : -ENODEV;
> +}
> +
> +static void sdrm_device_release(struct sdrm_device *sdrm)
> +{
> +     if (sdrm && atomic_dec_return(&sdrm->n_used) == INT_MIN) {
> +             sdrm_device_unbind(sdrm);
> +             sdrm_device_free(sdrm);
> +     }
> +}
> +
> +static int sdrm_fop_open(struct inode *inode, struct file *file)
> +{
> +     struct drm_device *ddev;
> +     int r;
> +
> +     mutex_lock(&sdrm_lock);
> +     r = drm_open(inode, file);
> +     if (r >= 0) {
> +             ddev = file->private_data;
> +             r = sdrm_device_acquire(ddev->dev_private);
> +             if (r < 0)
> +                     drm_release(inode, file);
> +     }
> +     mutex_unlock(&sdrm_lock);
> +
> +     return r;
> +}
> +
> +static int sdrm_fop_release(struct inode *inode, struct file *file)
> +{
> +     struct drm_file *dfile = file->private_data;
> +     struct drm_device *ddev = dfile->minor->dev;
> +     struct sdrm_device *sdrm = ddev->dev_private;
> +     int res;
> +
> +     res = drm_release(inode, file);
> +     sdrm_device_release(sdrm);
> +     return res;
> +}
> +
> +static int sdrm_fop_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> +     struct drm_file *dfile = file->private_data;
> +     struct drm_device *dev = dfile->minor->dev;
> +     struct drm_gem_object *obj = NULL;
> +     struct drm_vma_offset_node *node;
> +     int r;
> +
> +     drm_vma_offset_lock_lookup(dev->vma_offset_manager);
> +     node = drm_vma_offset_exact_lookup_locked(dev->vma_offset_manager,
> +                                               vma->vm_pgoff,
> +                                               vma_pages(vma));
> +     if (likely(node)) {
> +             obj = container_of(node, struct drm_gem_object, vma_node);
> +             if (!kref_get_unless_zero(&obj->refcount))
> +                     obj = NULL;
> +     }
> +     drm_vma_offset_unlock_lookup(dev->vma_offset_manager);
> +
> +     if (!obj)
> +             return -EINVAL;
> +
> +     if (!drm_vma_node_is_allowed(node, dfile)) {
> +             drm_gem_object_unreference_unlocked(obj);
> +             return -EACCES;
> +     }
> +
> +     if (vma->vm_file)
> +             fput(vma->vm_file);
> +     vma->vm_file = get_file(obj->filp);
> +     vma->vm_pgoff = 0;
> +
> +     r = obj->filp->f_op->mmap(obj->filp, vma);
> +     drm_gem_object_unreference_unlocked(obj);
> +     return r;
> +}
> +
> +static int sdrm_simplefb_probe(struct platform_device *pdev)
> +{
> +     struct simplefb_platform_data hw_mode;
> +     struct simplefb_format hw_format;
> +     struct sdrm_device *sdrm = NULL;
> +     struct sdrm_hw *hw = NULL;
> +     struct resource *hw_mem;
> +     u32 hw_bpp;
> +     int r;
> +
> +     r = sdrm_hw_identify(pdev, &hw_mode, &hw_format, &hw_mem, &hw_bpp);
> +     if (r < 0)
> +             goto error;
> +
> +     hw = sdrm_hw_new(&hw_mode, &hw_format, hw_mem, hw_bpp);
> +     if (IS_ERR(hw)) {
> +             r = PTR_ERR(hw);
> +             hw = NULL;
> +             goto error;
> +     }
> +
> +     sdrm = sdrm_device_new(pdev, hw);
> +     if (IS_ERR(sdrm)) {
> +             r = PTR_ERR(sdrm);
> +             sdrm = NULL;
> +             goto error;
> +     }
> +     hw = NULL;
> +     platform_set_drvdata(pdev, sdrm);
> +
> +     r = sdrm_device_bind(sdrm);
> +     if (r < 0)
> +             goto error;
> +
> +     /* mark device as enabled and acquire bus ref */
> +     WARN_ON(atomic_read(&sdrm->n_used) != INT_MIN);
> +     atomic_set(&sdrm->n_used, 1);
> +
> +     r = drm_dev_register(sdrm->ddev, 0);
> +     if (r < 0) {
> +             /* mark device as disabled and drop bus ref */
> +             WARN_ON(atomic_add_return(INT_MIN, &sdrm->n_used) != INT_MIN);
> +             sdrm_device_release(sdrm);
> +             return r;
> +     }
> +
> +     dev_info(sdrm->ddev->dev, "initialized %s on minor %d\n",
> +              sdrm->ddev->driver->name, sdrm->ddev->primary->index);
> +
> +     return 0;
> +
> +error:
> +     sdrm_device_unbind(sdrm);
> +     sdrm_device_free(sdrm);
> +     sdrm_hw_free(hw);
> +     return r;
> +}
> +
> +static int sdrm_simplefb_remove(struct platform_device *pdev)
> +{
> +     struct sdrm_device *sdrm = platform_get_drvdata(pdev);
> +
> +     /* mark device as disabled */
> +     atomic_add(INT_MIN, &sdrm->n_used);
> +     sdrm_hw_unbind(sdrm->hw);
> +
> +     mutex_lock(&sdrm_lock);
> +     drm_dev_unregister(sdrm->ddev);
> +     sdrm_device_release(sdrm);
> +     mutex_unlock(&sdrm_lock);
> +
> +     return 0;
> +}

There is something wrong with the ref counting.
Load, unload is fine:

$ sudo modprobe simpledrm
$ echo 0 | sudo tee /sys/class/vtconsole/vtcon1/bind
$ sudo modprobe -r simpledrm
$ ls -l /dev/fb*
ls: cannot access /dev/fb*: No such file or directory

But if I run modetest in between it's not released:

$ sudo modprobe simpledrm
$ ./libdrm/tests/modetest/modetest -M "simpledrm" -s 23:1824x984
setting mode 1824x984-60Hz at XR24 on connectors 23, crtc 25
$ echo 0 | sudo tee /sys/class/vtconsole/vtcon1/bind
$ sudo modprobe -r simpledrm
$ ls -l /dev/fb*
crw-rw---- 1 root video 29, 0 Sep  5 15:43 /dev/fb0


Noralf.

> +
> +static const struct file_operations sdrm_drm_fops = {
> +     .owner = THIS_MODULE,
> +     .open = sdrm_fop_open,
> +     .release = sdrm_fop_release,
> +     .mmap = sdrm_fop_mmap,
> +     .poll = drm_poll,
> +     .read = drm_read,
> +     .unlocked_ioctl = drm_ioctl,
> +#ifdef CONFIG_COMPAT
> +     .compat_ioctl = drm_compat_ioctl,
> +#endif
> +     .llseek = noop_llseek,
> +};
> +
> +static struct drm_driver sdrm_drm_driver = {
> +     .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
> +     .fops = &sdrm_drm_fops,
> +
> +     .gem_free_object = sdrm_bo_free,
> +
> +     .dumb_create = sdrm_dumb_create,
> +     .dumb_map_offset = sdrm_dumb_map_offset,
> +     .dumb_destroy = drm_gem_dumb_destroy,
> +
> +     .name = "simpledrm",
> +     .desc = "Simple firmware framebuffer DRM driver",
> +     .date = "20160901",
> +};
> +
> +static const struct of_device_id sdrm_simplefb_of_match[] = {
> +     { .compatible = "simple-framebuffer", },
> +     {},
> +};
> +MODULE_DEVICE_TABLE(of, sdrm_simplefb_of_match);
> +
> +static struct platform_driver sdrm_simplefb_driver = {
> +     .probe = sdrm_simplefb_probe,
> +     .remove = sdrm_simplefb_remove,
> +     .driver = {
> +             .name = "simple-framebuffer",
> +             .mod_name = KBUILD_MODNAME,
> +             .owner = THIS_MODULE,
> +             .of_match_table = sdrm_simplefb_of_match,
> +     },
> +};
> +
> +static int __init sdrm_init(void)
> +{
> +     int r;
> +
> +     r = platform_driver_register(&sdrm_simplefb_driver);
> +     if (r < 0)
> +             return r;
> +
> +     sdrm_of_bootstrap();
> +     return 0;
> +}
> +
> +static void __exit sdrm_exit(void)
> +{
> +     platform_driver_unregister(&sdrm_simplefb_driver);
> +}
> +
> +module_init(sdrm_init);
> +module_exit(sdrm_exit);
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver");
> +MODULE_ALIAS("platform:simple-framebuffer");
>

Reply via email to