On Thu, Aug 04, 2016 at 04:03:19PM +0200, Noralf Trønnes wrote: > 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. > > The driver was originally written by David Herrmann in 2014. > My main contribution is to make use of drm_simple_kms_helper and > rework the probe path to avoid use of the deprecated drm_platform_init() > and drm_driver.{load,unload}(). > > Cc: dh.herrmann@xxxxxxxxx > Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> Scrolled through it, only spotted the below nit (plus removing the legacy mmap stuff). But I'd like at least an ack from Dave. > +static int sdrm_simplefb_probe(struct platform_device *pdev) > +{ > + struct sdrm_device *sdrm; > + struct drm_device *ddev; > + int ret; > + > + ddev = drm_dev_alloc(&sdrm_drm_driver, &pdev->dev); > + if (!ddev) > + return -ENOMEM; > + > + sdrm = kzalloc(sizeof(*sdrm), GFP_KERNEL); > + if (!sdrm) > + goto err_free; > + > + ddev->platformdev = pdev; > + ddev->dev_private = sdrm; > + sdrm->ddev = ddev; > + > + ret = sdrm_pdev_init(sdrm); > + if (ret) > + goto err_free; > + > + ret = sdrm_drm_modeset_init(sdrm); > + if (ret) > + goto err_destroy; > + > + ret = drm_dev_register(ddev, 0); > + if (ret) > + goto err_cleanup; drm_dev_register needs to be last, after setting the drvdata. -Daniel > + > + platform_set_drvdata(pdev, ddev); > + > + DRM_INFO("Initialized %s on minor %d\n", ddev->driver->name, > + ddev->primary->index); > + > + return 0; > + > +err_cleanup: > + drm_mode_config_cleanup(ddev); > +err_destroy: > + sdrm_pdev_destroy(sdrm); > +err_free: > + drm_dev_unref(ddev); > + kfree(sdrm); > + > + return ret; > +} > + > +static int sdrm_simplefb_remove(struct platform_device *pdev) > +{ > + struct drm_device *ddev = platform_get_drvdata(pdev); > + struct sdrm_device *sdrm = ddev->dev_private; > + > + drm_dev_unregister(ddev); > + drm_mode_config_cleanup(ddev); > + > + /* protect fb_map removal against sdrm_blit() */ > + drm_modeset_lock_all(ddev); > + sdrm_pdev_destroy(sdrm); > + drm_modeset_unlock_all(ddev); > + > + drm_dev_unref(ddev); > + kfree(sdrm); > + > + return 0; > +} > + > +static const struct of_device_id simplefb_of_match[] = { > + { .compatible = "simple-framebuffer", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, 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 = simplefb_of_match, > + }, > +}; > + > +static int __init sdrm_init(void) > +{ > + return platform_driver_register(&sdrm_simplefb_driver); > +} > + > +static void __exit sdrm_exit(void) > +{ > + platform_driver_unregister(&sdrm_simplefb_driver); > +} > + > +module_init(sdrm_init); > +module_exit(sdrm_exit); > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("David Herrmann <dh.herrmann@xxxxxxxxx>"); > +MODULE_DESCRIPTION("Simple firmware framebuffer DRM driver"); > +MODULE_ALIAS("platform:simple-framebuffer"); > diff --git a/drivers/gpu/drm/simpledrm/simpledrm_gem.c b/drivers/gpu/drm/simpledrm/simpledrm_gem.c > new file mode 100644 > index 0000000..2d59632 > --- /dev/null > +++ b/drivers/gpu/drm/simpledrm/simpledrm_gem.c > @@ -0,0 +1,276 @@ > +/* > + * SimpleDRM firmware framebuffer driver > + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx> > + * > + * 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. > + */ > + > +#include <linux/dma-buf.h> > +#include <linux/errno.h> > +#include <linux/kernel.h> > +#include <linux/mm.h> > +#include <linux/module.h> > +#include <linux/string.h> > +#include <drm/drmP.h> > +#include <drm/drm_legacy.h> > +#include "simpledrm.h" > + > +int sdrm_gem_get_pages(struct sdrm_gem_object *obj) > +{ > + size_t num, i; > + > + if (obj->vmapping) > + return 0; > + > + if (obj->base.import_attach) { > + obj->vmapping = dma_buf_vmap(obj->base.import_attach->dmabuf); > + return !obj->vmapping ? -ENOMEM : 0; > + } > + > + num = obj->base.size >> PAGE_SHIFT; > + obj->pages = drm_malloc_ab(num, sizeof(*obj->pages)); > + if (!obj->pages) > + return -ENOMEM; > + > + for (i = 0; i < num; ++i) { > + obj->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); > + if (!obj->pages[i]) > + goto error; > + } > + > + obj->vmapping = vmap(obj->pages, num, 0, PAGE_KERNEL); > + if (!obj->vmapping) > + goto error; > + > + return 0; > + > +error: > + while (i > 0) > + __free_pages(obj->pages[--i], 0); > + > + drm_free_large(obj->pages); > + obj->pages = NULL; > + return -ENOMEM; > +} > + > +static void sdrm_gem_put_pages(struct sdrm_gem_object *obj) > +{ > + size_t num, i; > + > + if (!obj->vmapping) > + return; > + > + if (obj->base.import_attach) { > + dma_buf_vunmap(obj->base.import_attach->dmabuf, obj->vmapping); > + obj->vmapping = NULL; > + return; > + } > + > + vunmap(obj->vmapping); > + obj->vmapping = NULL; > + > + num = obj->base.size >> PAGE_SHIFT; > + for (i = 0; i < num; ++i) > + __free_pages(obj->pages[i], 0); > + > + drm_free_large(obj->pages); > + obj->pages = NULL; > +} > + > +struct sdrm_gem_object *sdrm_gem_alloc_object(struct drm_device *ddev, > + size_t size) > +{ > + struct sdrm_gem_object *obj; > + > + WARN_ON(!size || (size & ~PAGE_MASK) != 0); > + > + obj = kzalloc(sizeof(*obj), GFP_KERNEL); > + if (!obj) > + return NULL; > + > + drm_gem_private_object_init(ddev, &obj->base, size); > + return obj; > +} > + > +void sdrm_gem_free_object(struct drm_gem_object *gobj) > +{ > + struct sdrm_gem_object *obj = to_sdrm_bo(gobj); > + struct drm_device *ddev = gobj->dev; > + > + if (obj->pages) { > + /* kill all user-space mappings */ > + drm_vma_node_unmap(&gobj->vma_node, > + ddev->anon_inode->i_mapping); > + sdrm_gem_put_pages(obj); > + } > + > + if (gobj->import_attach) > + drm_prime_gem_destroy(gobj, obj->sg); > + > + drm_gem_free_mmap_offset(gobj); > + drm_gem_object_release(gobj); > + kfree(obj); > +} > + > +int sdrm_dumb_create(struct drm_file *dfile, struct drm_device *ddev, > + struct drm_mode_create_dumb *args) > +{ > + struct sdrm_gem_object *obj; > + int r; > + > + if (args->flags) > + return -EINVAL; > + > + /* overflow checks are done by DRM core */ > + args->pitch = (args->bpp + 7) / 8 * args->width; > + args->size = PAGE_ALIGN(args->pitch * args->height); > + > + obj = sdrm_gem_alloc_object(ddev, args->size); > + if (!obj) > + return -ENOMEM; > + > + r = drm_gem_handle_create(dfile, &obj->base, &args->handle); > + if (r) { > + drm_gem_object_unreference_unlocked(&obj->base); > + return r; > + } > + > + /* handle owns a reference */ > + drm_gem_object_unreference_unlocked(&obj->base); > + return 0; > +} > + > +int sdrm_dumb_destroy(struct drm_file *dfile, struct drm_device *ddev, > + uint32_t handle) > +{ > + return drm_gem_handle_delete(dfile, handle); > +} > + > +int sdrm_dumb_map_offset(struct drm_file *dfile, struct drm_device *ddev, > + uint32_t handle, uint64_t *offset) > +{ > + struct drm_gem_object *gobj; > + int r; > + > + mutex_lock(&ddev->struct_mutex); > + > + gobj = drm_gem_object_lookup(dfile, handle); > + if (!gobj) { > + r = -ENOENT; > + goto out_unlock; > + } > + > + r = drm_gem_create_mmap_offset(gobj); > + if (r) > + goto out_unref; > + > + *offset = drm_vma_node_offset_addr(&gobj->vma_node); > + > +out_unref: > + drm_gem_object_unreference(gobj); > +out_unlock: > + mutex_unlock(&ddev->struct_mutex); > + return r; > +} > + > +int sdrm_drm_mmap(struct file *filp, struct vm_area_struct *vma) > +{ > + struct drm_file *priv = filp->private_data; > + struct drm_device *dev = priv->minor->dev; > + struct drm_vma_offset_node *node; > + struct drm_gem_object *gobj; > + struct sdrm_gem_object *obj; > + size_t size, i, num; > + int r; > + > + if (drm_device_is_unplugged(dev)) > + return -ENODEV; > + > + 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)); > + drm_vma_offset_unlock_lookup(dev->vma_offset_manager); > + > + if (!node) > + return drm_legacy_mmap(filp, vma); > + else if (!drm_vma_node_is_allowed(node, filp)) > + return -EACCES; > + > + gobj = container_of(node, struct drm_gem_object, vma_node); > + obj = to_sdrm_bo(gobj); > + size = drm_vma_node_size(node) << PAGE_SHIFT; > + if (size < vma->vm_end - vma->vm_start) > + return r; > + > + r = sdrm_gem_get_pages(obj); > + if (r < 0) > + return r; > + > + /* prevent dmabuf-imported mmap to user-space */ > + if (!obj->pages) > + return -EACCES; > + > + vma->vm_flags |= VM_DONTEXPAND; > + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); > + > + num = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; > + for (i = 0; i < num; ++i) { > + r = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE, > + obj->pages[i]); > + if (r < 0) { > + if (i > 0) > + zap_vma_ptes(vma, vma->vm_start, i * PAGE_SIZE); > + return r; > + } > + } > + > + return 0; > +} > + > +struct drm_gem_object *sdrm_gem_prime_import(struct drm_device *ddev, > + struct dma_buf *dma_buf) > +{ > + struct dma_buf_attachment *attach; > + struct sdrm_gem_object *obj; > + struct sg_table *sg; > + int ret; > + > + /* need to attach */ > + attach = dma_buf_attach(dma_buf, ddev->dev); > + if (IS_ERR(attach)) > + return ERR_CAST(attach); > + > + get_dma_buf(dma_buf); > + > + sg = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL); > + if (IS_ERR(sg)) { > + ret = PTR_ERR(sg); > + goto fail_detach; > + } > + > + /* > + * dma_buf_vmap() gives us a page-aligned mapping, so lets bump the > + * size of the dma-buf to the next page-boundary > + */ > + obj = sdrm_gem_alloc_object(ddev, PAGE_ALIGN(dma_buf->size)); > + if (!obj) { > + ret = -ENOMEM; > + goto fail_unmap; > + } > + > + obj->sg = sg; > + obj->base.import_attach = attach; > + > + return &obj->base; > + > +fail_unmap: > + dma_buf_unmap_attachment(attach, sg, DMA_BIDIRECTIONAL); > +fail_detach: > + dma_buf_detach(dma_buf, attach); > + dma_buf_put(dma_buf); > + return ERR_PTR(ret); > +} > diff --git a/drivers/gpu/drm/simpledrm/simpledrm_kms.c b/drivers/gpu/drm/simpledrm/simpledrm_kms.c > new file mode 100644 > index 0000000..6295a9f > --- /dev/null > +++ b/drivers/gpu/drm/simpledrm/simpledrm_kms.c > @@ -0,0 +1,276 @@ > +/* > + * SimpleDRM firmware framebuffer driver > + * Copyright (c) 2012-2014 David Herrmann <dh.herrmann@xxxxxxxxx> > + * > + * 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. > + */ > + > +#include <linux/errno.h> > +#include <linux/kernel.h> > +#include <linux/mm.h> > +#include <linux/module.h> > +#include <linux/string.h> > +#include <drm/drmP.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_crtc_helper.h> > +#include "simpledrm.h" > + > +static const uint32_t sdrm_formats[] = { > + DRM_FORMAT_RGB565, > + DRM_FORMAT_ARGB8888, > + DRM_FORMAT_XRGB8888, > +}; > + > +static int sdrm_conn_get_modes(struct drm_connector *conn) > +{ > + struct sdrm_device *sdrm = conn->dev->dev_private; > + struct drm_display_mode *mode; > + > + mode = drm_cvt_mode(sdrm->ddev, sdrm->fb_width, sdrm->fb_height, > + 60, false, false, false); > + if (!mode) > + return 0; > + > + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; > + drm_mode_set_name(mode); > + drm_mode_probed_add(conn, mode); > + > + return 1; > +} > + > +static const struct drm_connector_helper_funcs sdrm_conn_hfuncs = { > + .get_modes = sdrm_conn_get_modes, > + .best_encoder = drm_atomic_helper_best_encoder, > +}; > + > +static enum drm_connector_status sdrm_conn_detect(struct drm_connector *conn, > + bool force) > +{ > + /* > + * We simulate an always connected monitor. simple-fb doesn't > + * provide any way to detect whether the connector is active. Hence, > + * signal DRM core that it is always connected. > + */ > + > + return connector_status_connected; > +} > + > +static const struct drm_connector_funcs sdrm_conn_ops = { > + .dpms = drm_atomic_helper_connector_dpms, > + .reset = drm_atomic_helper_connector_reset, > + .detect = sdrm_conn_detect, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = drm_connector_cleanup, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > +}; > + > +static inline struct sdrm_device * > +pipe_to_sdrm(struct drm_simple_display_pipe *pipe) > +{ > + return container_of(pipe, struct sdrm_device, pipe); > +} > + > +static int sdrm_display_pipe_check(struct drm_simple_display_pipe *pipe, > + struct drm_plane_state *plane_state, > + struct drm_crtc_state *crtc_state) > +{ > + struct drm_display_mode *mode = &crtc_state->mode; > + struct sdrm_device *sdrm = pipe_to_sdrm(pipe); > + struct drm_framebuffer *fb = plane_state->fb; > + u32 x = plane_state->src_x >> 16; > + u32 y = plane_state->src_y >> 16; > + > + if (mode->hdisplay != sdrm->fb_width || > + mode->vdisplay != sdrm->fb_height) > + return -EINVAL; > + if (fb->width <= x || fb->height <= y || > + fb->width - x < sdrm->fb_width || fb->height - y < sdrm->fb_height) > + return -EINVAL; > + > + return 0; > +} > + > +void sdrm_display_pipe_update(struct drm_simple_display_pipe *pipe, > + struct drm_plane_state *plane_state) > +{ > + struct drm_framebuffer *fb = pipe->plane.state->fb; > + struct sdrm_device *sdrm = pipe_to_sdrm(pipe); > + > + if (fb) > + sdrm_dirty_all_locked(sdrm); > +} > + > +static void sdrm_crtc_send_vblank_event(struct drm_crtc *crtc) > +{ > + if (crtc->state && crtc->state->event) { > + spin_lock_irq(&crtc->dev->event_lock); > + drm_crtc_send_vblank_event(crtc, crtc->state->event); > + spin_unlock_irq(&crtc->dev->event_lock); > + crtc->state->event = NULL; > + } > +} > + > +static void sdrm_display_pipe_enable(struct drm_simple_display_pipe *pipe, > + struct drm_crtc_state *crtc_state) > +{ > + sdrm_crtc_send_vblank_event(&pipe->crtc); > +} > + > +static void sdrm_display_pipe_disable(struct drm_simple_display_pipe *pipe) > +{ > + sdrm_crtc_send_vblank_event(&pipe->crtc); > +} > + > +static const struct drm_simple_display_pipe_funcs sdrm_pipe_funcs = { > + .check = sdrm_display_pipe_check, > + .update = sdrm_display_pipe_update, > + .enable = sdrm_display_pipe_enable, > + .disable = sdrm_display_pipe_disable, > +}; > + > +static int sdrm_fb_create_handle(struct drm_framebuffer *fb, > + struct drm_file *dfile, > + unsigned int *handle) > +{ > + struct sdrm_framebuffer *sfb = to_sdrm_fb(fb); > + > + return drm_gem_handle_create(dfile, &sfb->obj->base, handle); > +} > + > +static void sdrm_fb_destroy(struct drm_framebuffer *fb) > +{ > + struct sdrm_framebuffer *sfb = to_sdrm_fb(fb); > + > + drm_framebuffer_cleanup(fb); > + drm_gem_object_unreference_unlocked(&sfb->obj->base); > + kfree(sfb); > +} > + > +static const struct drm_framebuffer_funcs sdrm_fb_ops = { > + .create_handle = sdrm_fb_create_handle, > + .dirty = sdrm_dirty, > + .destroy = sdrm_fb_destroy, > +}; > + > +static struct drm_framebuffer *sdrm_fb_create(struct drm_device *ddev, > + struct drm_file *dfile, > + const struct drm_mode_fb_cmd2 *cmd) > +{ > + struct sdrm_framebuffer *fb; > + struct drm_gem_object *gobj; > + u32 bpp, size; > + int ret; > + void *err; > + > + if (cmd->flags) > + return ERR_PTR(-EINVAL); > + > + gobj = drm_gem_object_lookup(dfile, cmd->handles[0]); > + if (!gobj) > + return ERR_PTR(-EINVAL); > + > + fb = kzalloc(sizeof(*fb), GFP_KERNEL); > + if (!fb) { > + err = ERR_PTR(-ENOMEM); > + goto err_unref; > + } > + fb->obj = to_sdrm_bo(gobj); > + > + fb->base.pitches[0] = cmd->pitches[0]; > + fb->base.offsets[0] = cmd->offsets[0]; > + fb->base.width = cmd->width; > + fb->base.height = cmd->height; > + fb->base.pixel_format = cmd->pixel_format; > + drm_fb_get_bpp_depth(cmd->pixel_format, &fb->base.depth, > + &fb->base.bits_per_pixel); > + > + /* > + * width/height are already clamped into min/max_width/height range, > + * so overflows are not possible > + */ > + > + bpp = (fb->base.bits_per_pixel + 7) / 8; > + size = cmd->pitches[0] * cmd->height; > + if (!bpp || > + bpp > 4 || > + cmd->pitches[0] < bpp * fb->base.width || > + cmd->pitches[0] > 0xffffU || > + size + fb->base.offsets[0] < size || > + size + fb->base.offsets[0] > fb->obj->base.size) { > + err = ERR_PTR(-EINVAL); > + goto err_free; > + } > + > + ret = drm_framebuffer_init(ddev, &fb->base, &sdrm_fb_ops); > + if (ret < 0) { > + err = ERR_PTR(ret); > + goto err_free; > + } > + > + DRM_DEBUG_KMS("[FB:%d] pixel_format: %s\n", fb->base.base.id, > + drm_get_format_name(fb->base.pixel_format)); > + > + return &fb->base; > + > +err_free: > + kfree(fb); > +err_unref: > + drm_gem_object_unreference_unlocked(gobj); > + > + return err; > +} > + > +static const struct drm_mode_config_funcs sdrm_mode_config_ops = { > + .fb_create = sdrm_fb_create, > + .atomic_check = drm_atomic_helper_check, > + .atomic_commit = drm_atomic_helper_commit, > +}; > + > +int sdrm_drm_modeset_init(struct sdrm_device *sdrm) > +{ > + struct drm_connector *conn = &sdrm->conn; > + struct drm_device *ddev = sdrm->ddev; > + int ret; > + > + drm_mode_config_init(ddev); > + ddev->mode_config.min_width = 1; > + ddev->mode_config.min_height = 1; > + ddev->mode_config.max_width = 8192; > + ddev->mode_config.max_height = 8192; > + ddev->mode_config.preferred_depth = sdrm->fb_bpp; > + ddev->mode_config.funcs = &sdrm_mode_config_ops; > + > + drm_connector_helper_add(conn, &sdrm_conn_hfuncs); > + ret = drm_connector_init(ddev, conn, &sdrm_conn_ops, > + DRM_MODE_CONNECTOR_VIRTUAL); > + if (ret) > + goto err_cleanup; > + > + ret = drm_mode_create_dirty_info_property(ddev); > + if (ret) > + goto err_cleanup; > + > + drm_object_attach_property(&conn->base, > + ddev->mode_config.dirty_info_property, > + DRM_MODE_DIRTY_ON); > + > + ret = drm_simple_display_pipe_init(ddev, &sdrm->pipe, &sdrm_pipe_funcs, > + sdrm_formats, > + ARRAY_SIZE(sdrm_formats), conn); > + if (ret) > + goto err_cleanup; > + > + drm_mode_config_reset(ddev); > + > + return 0; > + > +err_cleanup: > + drm_mode_config_cleanup(ddev); > + > + return ret; > +} > -- > 2.8.2 > > _______________________________________________ > dri-devel mailing list > dri-devel@xxxxxxxxxxxxxxxxxxxxx > https://lists.freedesktop.org/mailman/listinfo/dri-devel -- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel