// SPDX-License-Identifier: GPL-2.0-only OR MIT
/* Copyright (c) 2023 Imagination Technologies Ltd. */
#include "pvr_device.h"
#include "pvr_gem.h"
#include "pvr_vm.h"
#include <drm/drm_gem.h>
#include <drm/drm_prime.h>
#include <linux/compiler.h>
#include <linux/compiler_attributes.h>
#include <linux/dma-buf.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/iosys-map.h>
#include <linux/log2.h>
#include <linux/mutex.h>
#include <linux/pagemap.h>
#include <linux/property.h>
#include <linux/refcount.h>
#include <linux/scatterlist.h>
static void pvr_gem_object_free(struct drm_gem_object *obj)
{
drm_gem_shmem_object_free(obj);
}
static int pvr_gem_mmap(struct drm_gem_object *gem_obj, struct vm_area_struct *vma)
{
struct pvr_gem_object *pvr_obj = gem_to_pvr_gem(gem_obj);
struct drm_gem_shmem_object *shmem_obj = shmem_gem_from_pvr_gem(pvr_obj);
if (!(pvr_obj->flags & DRM_PVR_BO_ALLOW_CPU_USERSPACE_ACCESS))
return -EINVAL;
return drm_gem_shmem_mmap(shmem_obj, vma);
}
static const struct drm_gem_object_funcs pvr_gem_object_funcs = {
.free = pvr_gem_object_free,
.print_info = drm_gem_shmem_object_print_info,
.pin = drm_gem_shmem_object_pin,
.unpin = drm_gem_shmem_object_unpin,
.get_sg_table = drm_gem_shmem_object_get_sg_table,
.vmap = drm_gem_shmem_object_vmap,
.vunmap = drm_gem_shmem_object_vunmap,
.mmap = pvr_gem_mmap,
.vm_ops = &drm_gem_shmem_vm_ops,
};
/**
* pvr_gem_object_flags_validate ( ) - Verify that a collection of PowerVR GEM
* mapping and / or creation flags form a valid combination .
* @ flags : PowerVR GEM mapping / creation flags to validate .
*
* This function explicitly allows kernel - only flags . All ioctl entrypoints
* should do their own validation as well as relying on this function .
*
* Return :
* * % true if @ flags contains valid mapping and / or creation flags , or
* * % false otherwise .
*/
static bool
pvr_gem_object_flags_validate(u64 flags)
{
static const u64 invalid_combinations[] = {
/*
* Memory flagged as PM / FW - protected cannot be mapped to
* userspace . To make this explicit , we require that the two
* flags allowing each of these respective features are never
* specified together .
*/
(DRM_PVR_BO_PM_FW_PROTECT |
DRM_PVR_BO_ALLOW_CPU_USERSPACE_ACCESS),
};
/*
* Check for bits set in undefined regions . Reserved regions refer to
* options that can only be set by the kernel . These are explicitly
* allowed in most cases , and must be checked specifically in IOCTL
* callback code .
*/
if ((flags & PVR_BO_UNDEFINED_MASK) != 0 )
return false ;
/*
* Check for all combinations of flags marked as invalid in the array
* above .
*/
for (int i = 0 ; i < ARRAY_SIZE(invalid_combinations); ++i) {
u64 combo = invalid_combinations[i];
if ((flags & combo) == combo)
return false ;
}
return true ;
}
/**
* pvr_gem_object_into_handle ( ) - Convert a reference to an object into a
* userspace - accessible handle .
* @ pvr_obj : [ IN ] Target PowerVR - specific object .
* @ pvr_file : [ IN ] File to associate the handle with .
* @ handle : [ OUT ] Pointer to store the created handle in . Remains unmodified if
* an error is encountered .
*
* If an error is encountered , ownership of @ pvr_obj will not have been
* transferred . If this function succeeds , however , further use of @ pvr_obj is
* considered undefined behaviour unless another reference to it is explicitly
* held .
*
* Return :
* * 0 on success , or
* * Any error encountered while attempting to allocate a handle on @ pvr_file .
*/
int
pvr_gem_object_into_handle(struct pvr_gem_object *pvr_obj,
struct pvr_file *pvr_file, u32 *handle)
{
struct drm_gem_object *gem_obj = gem_from_pvr_gem(pvr_obj);
struct drm_file *file = from_pvr_file(pvr_file);
u32 new_handle;
int err;
err = drm_gem_handle_create(file, gem_obj, &new_handle);
if (err)
return err;
/*
* Release our reference to @ pvr_obj , effectively transferring
* ownership to the handle .
*/
pvr_gem_object_put(pvr_obj);
/*
* Do not store the new handle in @ handle until no more errors can
* occur .
*/
*handle = new_handle;
return 0 ;
}
/**
* pvr_gem_object_from_handle ( ) - Obtain a reference to an object from a
* userspace handle .
* @ pvr_file : PowerVR - specific file to which @ handle is associated .
* @ handle : Userspace handle referencing the target object .
*
* On return , @ handle always maintains its reference to the requested object
* ( if it had one in the first place ) . If this function succeeds , the returned
* object will hold an additional reference . When the caller is finished with
* the returned object , they should call pvr_gem_object_put ( ) on it to release
* this reference .
*
* Return :
* * A pointer to the requested PowerVR - specific object on success , or
* * % NULL otherwise .
*/
struct pvr_gem_object *
pvr_gem_object_from_handle(struct pvr_file *pvr_file, u32 handle)
{
struct drm_file *file = from_pvr_file(pvr_file);
struct drm_gem_object *gem_obj;
gem_obj = drm_gem_object_lookup(file, handle);
if (!gem_obj)
return NULL;
return gem_to_pvr_gem(gem_obj);
}
/**
* pvr_gem_object_vmap ( ) - Map a PowerVR GEM object into CPU virtual address
* space .
* @ pvr_obj : Target PowerVR GEM object .
*
* Once the caller is finished with the CPU mapping , they must call
* pvr_gem_object_vunmap ( ) on @ pvr_obj .
*
* If @ pvr_obj is CPU - cached , dma_sync_sgtable_for_cpu ( ) is called to make
* sure the CPU mapping is consistent .
*
* Return :
* * A pointer to the CPU mapping on success ,
* * - % ENOMEM if the mapping fails , or
* * Any error encountered while attempting to acquire a reference to the
* backing pages for @ pvr_obj .
*/
void *
pvr_gem_object_vmap(struct pvr_gem_object *pvr_obj)
{
struct drm_gem_shmem_object *shmem_obj = shmem_gem_from_pvr_gem(pvr_obj);
struct drm_gem_object *obj = gem_from_pvr_gem(pvr_obj);
struct iosys_map map;
int err;
dma_resv_lock(obj->resv, NULL);
err = drm_gem_shmem_vmap_locked(shmem_obj, &map);
if (err)
goto err_unlock;
if (pvr_obj->flags & PVR_BO_CPU_CACHED) {
struct device *dev = shmem_obj->base.dev->dev;
/* If shmem_obj->sgt is NULL, that means the buffer hasn't been mapped
* in GPU space yet .
*/
if (shmem_obj->sgt)
dma_sync_sgtable_for_cpu(dev, shmem_obj->sgt, DMA_BIDIRECTIONAL);
}
dma_resv_unlock(obj->resv);
return map.vaddr;
err_unlock:
dma_resv_unlock(obj->resv);
return ERR_PTR(err);
}
/**
* pvr_gem_object_vunmap ( ) - Unmap a PowerVR memory object from CPU virtual
* address space .
* @ pvr_obj : Target PowerVR GEM object .
*
* If @ pvr_obj is CPU - cached , dma_sync_sgtable_for_device ( ) is called to make
* sure the GPU mapping is consistent .
*/
void
pvr_gem_object_vunmap(struct pvr_gem_object *pvr_obj)
{
struct drm_gem_shmem_object *shmem_obj = shmem_gem_from_pvr_gem(pvr_obj);
struct iosys_map map = IOSYS_MAP_INIT_VADDR(shmem_obj->vaddr);
struct drm_gem_object *obj = gem_from_pvr_gem(pvr_obj);
if (WARN_ON(!map.vaddr))
return ;
dma_resv_lock(obj->resv, NULL);
if (pvr_obj->flags & PVR_BO_CPU_CACHED) {
struct device *dev = shmem_obj->base.dev->dev;
/* If shmem_obj->sgt is NULL, that means the buffer hasn't been mapped
* in GPU space yet .
*/
if (shmem_obj->sgt)
dma_sync_sgtable_for_device(dev, shmem_obj->sgt, DMA_BIDIRECTIONAL);
}
drm_gem_shmem_vunmap_locked(shmem_obj, &map);
dma_resv_unlock(obj->resv);
}
/**
* pvr_gem_object_zero ( ) - Zeroes the physical memory behind an object .
* @ pvr_obj : Target PowerVR GEM object .
*
* Return :
* * 0 on success , or
* * Any error encountered while attempting to map @ pvr_obj to the CPU ( see
* pvr_gem_object_vmap ( ) ) .
*/
static int
pvr_gem_object_zero(struct pvr_gem_object *pvr_obj)
{
void *cpu_ptr;
cpu_ptr = pvr_gem_object_vmap(pvr_obj);
if (IS_ERR(cpu_ptr))
return PTR_ERR(cpu_ptr);
memset(cpu_ptr, 0 , pvr_gem_object_size(pvr_obj));
/* Make sure the zero-ing is done before vumap-ing the object. */
wmb();
pvr_gem_object_vunmap(pvr_obj);
return 0 ;
}
/**
* pvr_gem_create_object ( ) - Allocate and pre - initializes a pvr_gem_object
* @ drm_dev : DRM device creating this object .
* @ size : Size of the object to allocate in bytes .
*
* Return :
* * The new pre - initialized GEM object on success ,
* * - ENOMEM if the allocation failed .
*/
struct drm_gem_object *pvr_gem_create_object(struct drm_device *drm_dev, size_t size)
{
struct drm_gem_object *gem_obj;
struct pvr_gem_object *pvr_obj;
pvr_obj = kzalloc(sizeof (*pvr_obj), GFP_KERNEL);
if (!pvr_obj)
return ERR_PTR(-ENOMEM);
gem_obj = gem_from_pvr_gem(pvr_obj);
gem_obj->funcs = &pvr_gem_object_funcs;
return gem_obj;
}
/**
* pvr_gem_object_create ( ) - Creates a PowerVR - specific buffer object .
* @ pvr_dev : Target PowerVR device .
* @ size : Size of the object to allocate in bytes . Must be greater than zero .
* Any value which is not an exact multiple of the system page size will be
* rounded up to satisfy this condition .
* @ flags : Options which affect both this operation and future mapping
* operations performed on the returned object . Must be a combination of
* DRM_PVR_BO_ * and / or PVR_BO_ * flags .
*
* The created object may be larger than @ size , but can never be smaller . To
* get the exact size , call pvr_gem_object_size ( ) on the returned pointer .
*
* Return :
* * The newly - minted PowerVR - specific buffer object on success ,
* * - % EINVAL if @ size is zero or @ flags is not valid ,
* * - % ENOMEM if sufficient physical memory cannot be allocated , or
* * Any other error returned by drm_gem_create_mmap_offset ( ) .
*/
struct pvr_gem_object *
pvr_gem_object_create(struct pvr_device *pvr_dev, size_t size, u64 flags)
{
struct drm_device *drm_dev = from_pvr_device(pvr_dev);
struct drm_gem_shmem_object *shmem_obj;
struct pvr_gem_object *pvr_obj;
struct sg_table *sgt;
int err;
/* Verify @size and @flags before continuing. */
if (size == 0 || !pvr_gem_object_flags_validate(flags))
return ERR_PTR(-EINVAL);
if (device_get_dma_attr(drm_dev->dev) == DEV_DMA_COHERENT)
flags |= PVR_BO_CPU_CACHED;
shmem_obj = drm_gem_shmem_create(drm_dev, size);
if (IS_ERR(shmem_obj))
return ERR_CAST(shmem_obj);
shmem_obj->pages_mark_dirty_on_put = true ;
shmem_obj->map_wc = !(flags & PVR_BO_CPU_CACHED);
pvr_obj = shmem_gem_to_pvr_gem(shmem_obj);
pvr_obj->flags = flags;
sgt = drm_gem_shmem_get_pages_sgt(shmem_obj);
if (IS_ERR(sgt)) {
err = PTR_ERR(sgt);
goto err_shmem_object_free;
}
dma_sync_sgtable_for_device(drm_dev->dev, sgt, DMA_BIDIRECTIONAL);
/*
* Do this last because pvr_gem_object_zero ( ) requires a fully
* configured instance of struct pvr_gem_object .
*/
pvr_gem_object_zero(pvr_obj);
return pvr_obj;
err_shmem_object_free:
drm_gem_shmem_free(shmem_obj);
return ERR_PTR(err);
}
/**
* pvr_gem_get_dma_addr ( ) - Get DMA address for given offset in object
* @ pvr_obj : Pointer to object to lookup address in .
* @ offset : Offset within object to lookup address at .
* @ dma_addr_out : Pointer to location to store DMA address .
*
* Returns :
* * 0 on success , or
* * - % EINVAL if object is not currently backed , or if @ offset is out of valid
* range for this object .
*/
int
pvr_gem_get_dma_addr(struct pvr_gem_object *pvr_obj, u32 offset,
dma_addr_t *dma_addr_out)
{
struct drm_gem_shmem_object *shmem_obj = shmem_gem_from_pvr_gem(pvr_obj);
u32 accumulated_offset = 0 ;
struct scatterlist *sgl;
unsigned int sgt_idx;
WARN_ON(!shmem_obj->sgt);
for_each_sgtable_dma_sg(shmem_obj->sgt, sgl, sgt_idx) {
u32 new_offset = accumulated_offset + sg_dma_len(sgl);
if (offset >= accumulated_offset && offset < new_offset) {
*dma_addr_out = sg_dma_address(sgl) +
(offset - accumulated_offset);
return 0 ;
}
accumulated_offset = new_offset;
}
return -EINVAL;
}
Messung V0.5 in Prozent C=95 H=92 G=93
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland