[PATCH v5 6/7] drm: add SimpleDRM driver
Noralf Trønnes
noralf at tronnes.org
Mon Sep 5 16:39:55 UTC 2016
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 = ±
> +
> + 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");
>
More information about the dri-devel
mailing list