| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Copyright (C) 2023 Loongson Technology Corporation Limited | |
| */ | |
| #include <linux/dma-buf.h> | |
| #include <drm/drm_debugfs.h> | |
| #include <drm/drm_dumb_buffers.h> | |
| #include <drm/drm_file.h> | |
| #include <drm/drm_gem.h> | |
| #include <drm/drm_prime.h> | |
| #include <drm/drm_print.h> | |
| #include "lsdc_drv.h" | |
| #include "lsdc_gem.h" | |
| #include "lsdc_ttm.h" | |
| static int lsdc_gem_prime_pin(struct drm_gem_object *obj) | |
| { | |
| struct lsdc_bo *lbo = gem_to_lsdc_bo(obj); | |
| int ret; | |
| dma_resv_assert_held(obj->resv); | |
| ret = lsdc_bo_pin(lbo, LSDC_GEM_DOMAIN_GTT, NULL); | |
| if (likely(ret == 0)) | |
| lbo->sharing_count++; | |
| return ret; | |
| } | |
| static void lsdc_gem_prime_unpin(struct drm_gem_object *obj) | |
| { | |
| struct lsdc_bo *lbo = gem_to_lsdc_bo(obj); | |
| dma_resv_assert_held(obj->resv); | |
| lsdc_bo_unpin(lbo); | |
| if (lbo->sharing_count) | |
| lbo->sharing_count--; | |
| } | |
| static struct sg_table *lsdc_gem_prime_get_sg_table(struct drm_gem_object *obj) | |
| { | |
| struct ttm_buffer_object *tbo = to_ttm_bo(obj); | |
| struct ttm_tt *tt = tbo->ttm; | |
| if (!tt) { | |
| drm_err(obj->dev, "sharing a buffer without backing memory\n"); | |
| return ERR_PTR(-ENOMEM); | |
| } | |
| return drm_prime_pages_to_sg(obj->dev, tt->pages, tt->num_pages); | |
| } | |
| static void lsdc_gem_object_free(struct drm_gem_object *obj) | |
| { | |
| struct ttm_buffer_object *tbo = to_ttm_bo(obj); | |
| if (tbo) | |
| ttm_bo_fini(tbo); | |
| } | |
| static int lsdc_gem_object_vmap(struct drm_gem_object *obj, struct iosys_map *map) | |
| { | |
| struct ttm_buffer_object *tbo = to_ttm_bo(obj); | |
| struct lsdc_bo *lbo = to_lsdc_bo(tbo); | |
| int ret; | |
| if (lbo->vmap_count > 0) { | |
| ++lbo->vmap_count; | |
| goto out; | |
| } | |
| ret = lsdc_bo_pin(lbo, 0, NULL); | |
| if (unlikely(ret)) { | |
| drm_err(obj->dev, "pin %p for vmap failed\n", lbo); | |
| return ret; | |
| } | |
| ret = ttm_bo_vmap(tbo, &lbo->map); | |
| if (ret) { | |
| drm_err(obj->dev, "ttm bo vmap failed\n"); | |
| lsdc_bo_unpin(lbo); | |
| return ret; | |
| } | |
| lbo->vmap_count = 1; | |
| out: | |
| *map = lbo->map; | |
| return 0; | |
| } | |
| static void lsdc_gem_object_vunmap(struct drm_gem_object *obj, struct iosys_map *map) | |
| { | |
| struct ttm_buffer_object *tbo = to_ttm_bo(obj); | |
| struct lsdc_bo *lbo = to_lsdc_bo(tbo); | |
| if (unlikely(!lbo->vmap_count)) { | |
| drm_warn(obj->dev, "%p is not mapped\n", lbo); | |
| return; | |
| } | |
| --lbo->vmap_count; | |
| if (lbo->vmap_count == 0) { | |
| ttm_bo_vunmap(tbo, &lbo->map); | |
| lsdc_bo_unpin(lbo); | |
| } | |
| } | |
| static int lsdc_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) | |
| { | |
| struct ttm_buffer_object *tbo = to_ttm_bo(obj); | |
| int ret; | |
| ret = ttm_bo_mmap_obj(vma, tbo); | |
| if (unlikely(ret)) { | |
| drm_warn(obj->dev, "mmap %p failed\n", tbo); | |
| return ret; | |
| } | |
| drm_gem_object_put(obj); | |
| return 0; | |
| } | |
| static const struct drm_gem_object_funcs lsdc_gem_object_funcs = { | |
| .free = lsdc_gem_object_free, | |
| .export = drm_gem_prime_export, | |
| .pin = lsdc_gem_prime_pin, | |
| .unpin = lsdc_gem_prime_unpin, | |
| .get_sg_table = lsdc_gem_prime_get_sg_table, | |
| .vmap = lsdc_gem_object_vmap, | |
| .vunmap = lsdc_gem_object_vunmap, | |
| .mmap = lsdc_gem_object_mmap, | |
| }; | |
| struct drm_gem_object *lsdc_gem_object_create(struct drm_device *ddev, | |
| u32 domain, | |
| size_t size, | |
| bool kerenl, | |
| struct sg_table *sg, | |
| struct dma_resv *resv) | |
| { | |
| struct lsdc_device *ldev = to_lsdc(ddev); | |
| struct drm_gem_object *gobj; | |
| struct lsdc_bo *lbo; | |
| int ret; | |
| lbo = lsdc_bo_create(ddev, domain, size, kerenl, sg, resv); | |
| if (IS_ERR(lbo)) { | |
| ret = PTR_ERR(lbo); | |
| return ERR_PTR(ret); | |
| } | |
| if (!sg) { | |
| /* VRAM is filled with random data */ | |
| lsdc_bo_clear(lbo); | |
| } | |
| gobj = &lbo->tbo.base; | |
| gobj->funcs = &lsdc_gem_object_funcs; | |
| /* tracking the BOs we created */ | |
| mutex_lock(&ldev->gem.mutex); | |
| list_add_tail(&lbo->list, &ldev->gem.objects); | |
| mutex_unlock(&ldev->gem.mutex); | |
| return gobj; | |
| } | |
| struct drm_gem_object * | |
| lsdc_prime_import_sg_table(struct drm_device *ddev, | |
| struct dma_buf_attachment *attach, | |
| struct sg_table *sg) | |
| { | |
| struct dma_resv *resv = attach->dmabuf->resv; | |
| u64 size = attach->dmabuf->size; | |
| struct drm_gem_object *gobj; | |
| struct lsdc_bo *lbo; | |
| dma_resv_lock(resv, NULL); | |
| gobj = lsdc_gem_object_create(ddev, LSDC_GEM_DOMAIN_GTT, size, false, | |
| sg, resv); | |
| dma_resv_unlock(resv); | |
| if (IS_ERR(gobj)) { | |
| drm_err(ddev, "Failed to import sg table\n"); | |
| return gobj; | |
| } | |
| lbo = gem_to_lsdc_bo(gobj); | |
| lbo->sharing_count = 1; | |
| return gobj; | |
| } | |
| int lsdc_dumb_create(struct drm_file *file, struct drm_device *ddev, | |
| struct drm_mode_create_dumb *args) | |
| { | |
| struct lsdc_device *ldev = to_lsdc(ddev); | |
| const struct lsdc_desc *descp = ldev->descp; | |
| u32 domain = LSDC_GEM_DOMAIN_VRAM; | |
| struct drm_gem_object *gobj; | |
| int ret; | |
| ret = drm_mode_size_dumb(ddev, args, descp->pitch_align, 0); | |
| if (ret) | |
| return ret; | |
| /* Maximum single bo size allowed is the half vram size available */ | |
| if (args->size > ldev->vram_size / 2) { | |
| drm_err(ddev, "Requesting(%zuMiB) failed\n", (size_t)(args->size >> PAGE_SHIFT)); | |
| return -ENOMEM; | |
| } | |
| gobj = lsdc_gem_object_create(ddev, domain, args->size, false, NULL, NULL); | |
| if (IS_ERR(gobj)) { | |
| drm_err(ddev, "Failed to create gem object\n"); | |
| return PTR_ERR(gobj); | |
| } | |
| ret = drm_gem_handle_create(file, gobj, &args->handle); | |
| /* drop reference from allocate, handle holds it now */ | |
| drm_gem_object_put(gobj); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| void lsdc_gem_init(struct drm_device *ddev) | |
| { | |
| struct lsdc_device *ldev = to_lsdc(ddev); | |
| mutex_init(&ldev->gem.mutex); | |
| INIT_LIST_HEAD(&ldev->gem.objects); | |
| } | |
| int lsdc_show_buffer_object(struct seq_file *m, void *arg) | |
| { | |
| struct drm_info_node *node = (struct drm_info_node *)m->private; | |
| struct drm_device *ddev = node->minor->dev; | |
| struct lsdc_device *ldev = to_lsdc(ddev); | |
| struct lsdc_bo *lbo; | |
| unsigned int i; | |
| mutex_lock(&ldev->gem.mutex); | |
| i = 0; | |
| list_for_each_entry(lbo, &ldev->gem.objects, list) { | |
| struct ttm_buffer_object *tbo = &lbo->tbo; | |
| struct ttm_resource *resource = tbo->resource; | |
| seq_printf(m, "bo[%04u][%p]: size: %8zuKiB %s offset: %8llx\n", | |
| i, lbo, lsdc_bo_size(lbo) >> 10, | |
| lsdc_mem_type_to_str(resource->mem_type), | |
| lsdc_bo_gpu_offset(lbo)); | |
| i++; | |
| } | |
| mutex_unlock(&ldev->gem.mutex); | |
| seq_printf(m, "Pinned BO size: VRAM: %zuKiB, GTT: %zu KiB\n", | |
| ldev->vram_pinned_size >> 10, ldev->gtt_pinned_size >> 10); | |
| return 0; | |
| } |