| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <drm/drmP.h> |
| #include <drm/drm_gem.h> |
| #include <drm/drm_vma_manager.h> |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/dma-buf.h> |
| #include <linux/meson_ion.h> |
| |
| #include "meson_gem.h" |
| |
| #define to_am_meson_gem_obj(x) container_of(x,\ |
| struct am_meson_gem_object, base) |
| |
| #define ION_HEAP_ID_SYS 0 |
| static int am_meson_gem_alloc_ion_buff(struct am_meson_gem_object * |
| meson_gem_obj, int flags) |
| { |
| int fb; |
| size_t len; |
| u32 bscatter = 0; |
| struct dma_buf *dmabuf; |
| struct ion_buffer *buffer; |
| unsigned int id; |
| |
| if (!meson_gem_obj) |
| return -EINVAL; |
| |
| id = meson_ion_cma_heap_id_get(); |
| DRM_DEBUG("ion heap id=%d,size=0x%x,flags=0x%x\n", |
| id, (u32)meson_gem_obj->base.size, flags); |
| /* |
| *check flags to set different ion heap type. |
| *if flags is set to 0, need to use ion dma buffer. |
| */ |
| if (((flags & (MESON_USE_SCANOUT | MESON_USE_CURSOR)) != 0) || |
| flags == 0) { |
| fb = ion_alloc(meson_gem_obj->base.size, |
| (1 << id), 0); |
| } else { |
| fb = ion_alloc(meson_gem_obj->base.size, |
| (1 << ION_HEAP_ID_SYS), 0); |
| bscatter = 1; |
| } |
| |
| if (fb < 0) { |
| DRM_ERROR("FAILED, flags:0x%x.\n", flags); |
| return -ENOMEM; |
| } |
| dmabuf = dma_buf_get(fb); |
| if (IS_ERR(dmabuf)) |
| return PTR_ERR(dmabuf); |
| meson_gem_obj->fb = fb; |
| meson_gem_obj->base.dma_buf = dmabuf; |
| |
| buffer = (struct ion_buffer *)dmabuf->priv; |
| meson_ion_buffer_to_phys(buffer, |
| (phys_addr_t *)&meson_gem_obj->addr, |
| (size_t *)&len); |
| dma_buf_put(dmabuf); |
| meson_gem_obj->bscatter = bscatter; |
| DRM_DEBUG("sg_table:nents=%d,dma_addr=0x%x,length=%d,offset=%d\n", |
| buffer->sg_table->nents, |
| (u32)buffer->sg_table->sgl->dma_address, |
| buffer->sg_table->sgl->length, |
| buffer->sg_table->sgl->offset); |
| DRM_DEBUG("allocate size (%d) fb (%d) addr=0x%x.\n", |
| (u32)meson_gem_obj->base.size, fb, (u32)meson_gem_obj->addr); |
| return 0; |
| } |
| |
| static void am_meson_gem_free_ion_buf(struct drm_device *dev, |
| struct am_meson_gem_object * |
| meson_gem_obj) |
| { |
| } |
| |
| struct am_meson_gem_object *am_meson_gem_object_create(struct drm_device *dev, |
| unsigned int flags, |
| unsigned long size) |
| { |
| struct am_meson_gem_object *meson_gem_obj = NULL; |
| int ret; |
| |
| if (!size) { |
| DRM_ERROR("invalid size.\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| size = roundup(size, PAGE_SIZE); |
| meson_gem_obj = kzalloc(sizeof(*meson_gem_obj), GFP_KERNEL); |
| if (!meson_gem_obj) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = drm_gem_object_init(dev, &meson_gem_obj->base, size); |
| if (ret < 0) { |
| DRM_ERROR("failed to initialize gem object\n"); |
| goto error; |
| } |
| |
| ret = am_meson_gem_alloc_ion_buff(meson_gem_obj, flags); |
| if (ret < 0) { |
| drm_gem_object_release(&meson_gem_obj->base); |
| goto error; |
| } |
| |
| return meson_gem_obj; |
| |
| error: |
| kfree(meson_gem_obj); |
| return ERR_PTR(ret); |
| } |
| |
| void am_meson_gem_object_free(struct drm_gem_object *obj) |
| { |
| struct am_meson_gem_object *meson_gem_obj = |
| (struct am_meson_gem_object *)to_am_meson_gem_obj(obj); |
| |
| DRM_DEBUG("meson_gem_obj %p handle count = %d\n", |
| meson_gem_obj, obj->handle_count); |
| |
| if (!obj->import_attach) |
| am_meson_gem_free_ion_buf(obj->dev, meson_gem_obj); |
| else |
| DRM_ERROR("Not support import buffer from other driver.\n"); |
| |
| /* release file pointer to gem object. */ |
| drm_gem_object_release(obj); |
| |
| kfree(meson_gem_obj); |
| meson_gem_obj = NULL; |
| } |
| |
| int am_meson_gem_object_mmap(struct am_meson_gem_object *obj, |
| struct vm_area_struct *vma) |
| { |
| int ret = 0; |
| struct ion_buffer *buffer; |
| struct dma_buf *dmabuf; |
| |
| /* |
| * Clear the VM_PFNMAP flag that was set by drm_gem_mmap(), and set the |
| * vm_pgoff (used as a fake buffer offset by DRM) to 0 as we want to map |
| * the whole buffer. |
| */ |
| vma->vm_flags &= ~VM_PFNMAP; |
| vma->vm_pgoff = 0; |
| |
| if (obj->base.import_attach) { |
| DRM_ERROR("Not support import buffer from other driver.\n"); |
| } else { |
| if (IS_ERR(obj)) |
| return PTR_ERR(obj); |
| dmabuf = obj->base.dma_buf; |
| if (IS_ERR(dmabuf)) |
| return PTR_ERR(dmabuf); |
| buffer = (struct ion_buffer *)dmabuf->priv; |
| |
| if (!buffer->heap->ops->map_user) { |
| DRM_ERROR("heap does not define map to userspace\n"); |
| ret = -EINVAL; |
| } else { |
| DRM_DEBUG("buffer->flags=0x%x\n", (u32)buffer->flags); |
| if (!(buffer->flags & ION_FLAG_CACHED)) |
| vma->vm_page_prot = |
| pgprot_writecombine(vma->vm_page_prot); |
| mutex_lock(&buffer->lock); |
| /* now map it to userspace */ |
| ret = buffer->heap->ops->map_user(buffer->heap, |
| buffer, vma); |
| mutex_unlock(&buffer->lock); |
| } |
| } |
| |
| if (ret) { |
| DRM_ERROR("failure mapping buffer to userspace (%d)\n", |
| ret); |
| drm_gem_vm_close(vma); |
| } |
| |
| return ret; |
| } |
| |
| int am_meson_gem_mmap(struct file *filp, |
| struct vm_area_struct *vma) |
| { |
| struct drm_gem_object *obj; |
| struct am_meson_gem_object *meson_gem_obj; |
| int ret; |
| |
| ret = drm_gem_mmap(filp, vma); |
| if (ret) |
| return ret; |
| |
| obj = vma->vm_private_data; |
| meson_gem_obj = to_am_meson_gem_obj(obj); |
| DRM_DEBUG("meson_gem_obj %p.\n", meson_gem_obj); |
| |
| ret = am_meson_gem_object_mmap(meson_gem_obj, vma); |
| |
| return ret; |
| } |
| |
| phys_addr_t am_meson_gem_object_get_phyaddr(struct meson_drm *drm, |
| struct am_meson_gem_object * |
| meson_gem) |
| { |
| if (meson_gem->fb < 0) { |
| DRM_INFO("%s fb invalid\n", __func__); |
| return -1; |
| } |
| return meson_gem->addr; |
| } |
| EXPORT_SYMBOL(am_meson_gem_object_get_phyaddr); |
| |
| int am_meson_gem_dumb_create(struct drm_file *file_priv, |
| struct drm_device *dev, |
| struct drm_mode_create_dumb *args) |
| { |
| int ret = 0; |
| struct am_meson_gem_object *meson_gem_obj; |
| int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); |
| |
| args->pitch = ALIGN(min_pitch, 64); |
| if (args->size < args->pitch * args->height) |
| args->size = args->pitch * args->height; |
| |
| args->size = round_up(args->size, PAGE_SIZE); |
| |
| meson_gem_obj = am_meson_gem_object_create(dev, args->flags, |
| args->size); |
| if (IS_ERR(meson_gem_obj)) |
| return PTR_ERR(meson_gem_obj); |
| |
| /* |
| * allocate a id of idr table where the obj is registered |
| * and handle has the id what user can see. |
| */ |
| ret = drm_gem_handle_create(file_priv, |
| &meson_gem_obj->base, &args->handle); |
| /* drop reference from allocate - handle holds it now. */ |
| drm_gem_object_unreference_unlocked(&meson_gem_obj->base); |
| if (ret) { |
| DRM_ERROR("create dumb handle failed %d\n", ret); |
| return ret; |
| } |
| |
| DRM_DEBUG("create dumb 0x%p with gem handle (0x%x) flags=0x%x\n", |
| meson_gem_obj, args->handle, args->flags); |
| DRM_DEBUG("bpp=%d,pitch=%d,size=0x%x(%d)\n", |
| args->bpp, args->pitch, (u32)args->size, (u32)args->size); |
| return 0; |
| } |
| |
| int am_meson_gem_dumb_destroy(struct drm_file *file, |
| struct drm_device *dev, |
| u32 handle) |
| { |
| struct am_meson_gem_object *meson_gem_obj; |
| struct drm_gem_object *obj; |
| |
| DRM_DEBUG("destroy dumb with handle (0x%x)\n", handle); |
| spin_lock(&file->table_lock); |
| obj = idr_find(&file->object_idr, handle); |
| spin_unlock(&file->table_lock); |
| if (IS_ERR_OR_NULL(obj)) |
| return -EINVAL; |
| |
| meson_gem_obj = (struct am_meson_gem_object *)to_am_meson_gem_obj(obj); |
| put_unused_fd(meson_gem_obj->fb); |
| meson_gem_obj->fb = -1; |
| drm_gem_handle_delete(file, handle); |
| return 0; |
| } |
| |
| int am_meson_gem_dumb_map_offset(struct drm_file *file_priv, |
| struct drm_device *dev, |
| u32 handle, |
| u64 *offset) |
| { |
| struct drm_gem_object *obj; |
| int ret = 0; |
| |
| mutex_lock(&dev->struct_mutex); |
| |
| /* |
| * get offset of memory allocated for drm framebuffer. |
| * - this callback would be called by user application |
| * with DRM_IOCTL_MODE_MAP_DUMB command. |
| */ |
| obj = drm_gem_object_lookup(file_priv, handle); |
| if (!obj) { |
| DRM_ERROR("failed to lookup gem object.\n"); |
| ret = -EINVAL; |
| goto unlock; |
| } |
| |
| ret = drm_gem_create_mmap_offset(obj); |
| if (ret) |
| goto out; |
| |
| *offset = drm_vma_node_offset_addr(&obj->vma_node); |
| DRM_DEBUG("offset = 0x%lx\n", (unsigned long)*offset); |
| |
| out: |
| drm_gem_object_unreference(obj); |
| unlock: |
| mutex_unlock(&dev->struct_mutex); |
| return ret; |
| } |
| |
| int am_meson_gem_create_ioctl(struct drm_device *dev, void *data, |
| struct drm_file *file_priv) |
| { |
| struct am_meson_gem_object *meson_gem_obj; |
| struct drm_meson_gem_create *args = data; |
| int ret = 0; |
| |
| args->size = round_up(args->size, PAGE_SIZE); |
| |
| meson_gem_obj = am_meson_gem_object_create(dev, args->flags, |
| args->size); |
| if (IS_ERR(meson_gem_obj)) |
| return PTR_ERR(meson_gem_obj); |
| |
| /* |
| * allocate a id of idr table where the obj is registered |
| * and handle has the id what user can see. |
| */ |
| ret = drm_gem_handle_create(file_priv, |
| &meson_gem_obj->base, &args->handle); |
| /* drop reference from allocate - handle holds it now. */ |
| drm_gem_object_unreference_unlocked(&meson_gem_obj->base); |
| if (ret) { |
| DRM_ERROR("create dumb handle failed %d\n", ret); |
| return ret; |
| } |
| |
| DRM_DEBUG("create dumb %p with gem handle (0x%x)\n", |
| meson_gem_obj, args->handle); |
| return 0; |
| } |
| |
| int am_meson_gem_create(struct meson_drm *drmdrv) |
| { |
| /*TODO??*/ |
| return 0; |
| } |
| |
| void am_meson_gem_cleanup(struct meson_drm *drmdrv) |
| { |
| /*TODO??*/ |
| } |
| |
| struct sg_table *am_meson_gem_prime_get_sg_table(struct drm_gem_object *obj) |
| { |
| struct am_meson_gem_object *meson_gem_obj; |
| struct sg_table *dst_table = NULL; |
| struct scatterlist *dst_sg = NULL; |
| struct sg_table *src_table = NULL; |
| struct scatterlist *src_sg = NULL; |
| struct ion_buffer *buffer; |
| struct dma_buf *dmabuf; |
| int ret, i; |
| |
| meson_gem_obj = (struct am_meson_gem_object *)to_am_meson_gem_obj(obj); |
| DRM_DEBUG("meson_gem_obj %p.\n", meson_gem_obj); |
| |
| if (!meson_gem_obj->base.import_attach) { |
| dmabuf = meson_gem_obj->base.dma_buf; |
| if (IS_ERR(dmabuf)) { |
| ret = -ENOMEM; |
| return ERR_PTR(ret); |
| } |
| |
| buffer = (struct ion_buffer *)dmabuf->priv; |
| src_table = buffer->sg_table; |
| dst_table = kmalloc(sizeof(*dst_table), GFP_KERNEL); |
| if (!dst_table) { |
| ret = -ENOMEM; |
| return ERR_PTR(ret); |
| } |
| |
| ret = sg_alloc_table(dst_table, src_table->nents, GFP_KERNEL); |
| if (ret) { |
| kfree(dst_table); |
| return ERR_PTR(ret); |
| } |
| |
| dst_sg = dst_table->sgl; |
| src_sg = src_table->sgl; |
| for (i = 0; i < src_table->nents; i++) { |
| sg_set_page(dst_sg, sg_page(src_sg), src_sg->length, 0); |
| dst_sg = sg_next(dst_sg); |
| src_sg = sg_next(src_sg); |
| } |
| return dst_table; |
| } |
| DRM_ERROR("Not support import buffer from other driver.\n"); |
| return NULL; |
| } |
| |
| struct drm_gem_object * |
| am_meson_gem_prime_import_sg_table(struct drm_device *dev, |
| struct dma_buf_attachment *attach, |
| struct sg_table *sgt) |
| { |
| struct am_meson_gem_object *meson_gem_obj; |
| int ret; |
| |
| meson_gem_obj = kzalloc(sizeof(*meson_gem_obj), GFP_KERNEL); |
| if (!meson_gem_obj) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = drm_gem_object_init(dev, |
| &meson_gem_obj->base, |
| attach->dmabuf->size); |
| if (ret < 0) { |
| DRM_ERROR("failed to initialize gem object\n"); |
| kfree(meson_gem_obj); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| DRM_DEBUG("%p, sg_table %p\n", meson_gem_obj, sgt); |
| /*meson_gem_obj->sgt = sgt;*/ |
| return &meson_gem_obj->base; |
| } |
| |
| void *am_meson_gem_prime_vmap(struct drm_gem_object *obj) |
| { |
| DRM_DEBUG("obj %p.\n", obj); |
| |
| return NULL; |
| } |
| |
| void am_meson_gem_prime_vunmap(struct drm_gem_object *obj, |
| void *vaddr) |
| { |
| DRM_DEBUG("nothing to do.\n"); |
| } |
| |
| int am_meson_gem_prime_mmap(struct drm_gem_object *obj, |
| struct vm_area_struct *vma) |
| { |
| struct am_meson_gem_object *meson_gem_obj; |
| int ret; |
| |
| ret = drm_gem_mmap_obj(obj, obj->size, vma); |
| if (ret < 0) |
| return ret; |
| |
| meson_gem_obj = to_am_meson_gem_obj(obj); |
| DRM_DEBUG("meson_gem_obj %p.\n", meson_gem_obj); |
| |
| return am_meson_gem_object_mmap(meson_gem_obj, vma); |
| } |