| /* -*- mode: c; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ |
| /* vi: set ts=8 sw=8 sts=8: */ |
| /*************************************************************************/ /*! |
| @File pvr_sync_file.c |
| @Title Kernel driver for Android's sync mechanism |
| @Codingstyle LinuxKernel |
| @Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved |
| @License MIT |
| |
| The contents of this file are subject to the MIT license as set out below. |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy |
| of this software and associated documentation files (the "Software"), to deal |
| in the Software without restriction, including without limitation the rights |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| copies of the Software, and to permit persons to whom the Software is |
| furnished to do so, subject to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in |
| all copies or substantial portions of the Software. |
| |
| This License is also included in this distribution in the file called |
| "MIT-COPYING". |
| |
| EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) THE SOFTWARE IS |
| PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING |
| BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
| PURPOSE AND NONINFRINGEMENT; AND (B) IN NO EVENT SHALL THE AUTHORS OR |
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ /**************************************************************************/ |
| |
| #include "services_kernel_client.h" |
| #include "pvr_drv.h" |
| #include "pvr_sync.h" |
| #include "pvr_fence.h" |
| #include "pvr_counting_timeline.h" |
| |
| #include "linux_sw_sync.h" |
| |
| #include <linux/version.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/sync_file.h> |
| #include <linux/file.h> |
| #include <linux/miscdevice.h> |
| #include <linux/uaccess.h> |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)) && !defined(CHROMIUMOS_KERNEL) |
| #define sync_file_user_name(s) ((s)->name) |
| #else |
| #define sync_file_user_name(s) ((s)->user_name) |
| #endif |
| |
| #define PVR_DUMPDEBUG_LOG(pfnDumpDebugPrintf, pvDumpDebugFile, fmt, ...) \ |
| do { \ |
| if (pfnDumpDebugPrintf) \ |
| pfnDumpDebugPrintf(pvDumpDebugFile, fmt, \ |
| ## __VA_ARGS__); \ |
| else \ |
| pr_err(fmt "\n", ## __VA_ARGS__); \ |
| } while (0) |
| |
| #define FILE_NAME "pvr_sync_file" |
| |
| struct sw_sync_create_fence_data { |
| __u32 value; |
| char name[32]; |
| __s32 fence; |
| }; |
| #define SW_SYNC_IOC_MAGIC 'W' |
| #define SW_SYNC_IOC_CREATE_FENCE \ |
| (_IOWR(SW_SYNC_IOC_MAGIC, 0, struct sw_sync_create_fence_data)) |
| #define SW_SYNC_IOC_INC _IOW(SW_SYNC_IOC_MAGIC, 1, __u32) |
| |
| /* Global data for the sync driver */ |
| static struct { |
| void *dev_cookie; |
| void *dbg_request_handle; |
| struct workqueue_struct *fence_status_wq; |
| struct pvr_fence_context *foreign_fence_context; |
| #if defined(NO_HARDWARE) |
| spinlock_t pvr_timeline_active_list_lock; |
| struct list_head pvr_timeline_active_list; |
| #endif |
| } pvr_sync_data; |
| |
| static const struct file_operations pvr_sync_fops; |
| |
| /* This is the actual timeline metadata. We might keep this around after the |
| * base sync driver has destroyed the pvr_sync_timeline_wrapper object. |
| */ |
| struct pvr_sync_timeline { |
| char name[32]; |
| struct file *file; |
| bool is_sw; |
| /* Fence context used for hw fences */ |
| struct pvr_fence_context *hw_fence_context; |
| /* Timeline and context for sw fences */ |
| struct pvr_counting_fence_timeline *sw_fence_timeline; |
| #if defined(NO_HARDWARE) |
| /* List of all timelines (used to advance all timelines in nohw builds) */ |
| struct list_head list; |
| #endif |
| }; |
| |
| static |
| void pvr_sync_free_checkpoint_list_mem(void *mem_ptr) |
| { |
| kfree(mem_ptr); |
| } |
| |
| #if defined(NO_HARDWARE) |
| /* function used to signal pvr fence in nohw builds */ |
| static |
| void pvr_sync_nohw_signal_fence(void *fence_data_to_signal) |
| { |
| struct pvr_sync_timeline *this_timeline; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pvr_sync_data.pvr_timeline_active_list_lock, flags); |
| list_for_each_entry(this_timeline, &pvr_sync_data.pvr_timeline_active_list, list) { |
| pvr_fence_context_signal_fences_nohw(this_timeline->hw_fence_context); |
| } |
| spin_unlock_irqrestore(&pvr_sync_data.pvr_timeline_active_list_lock, flags); |
| } |
| #endif |
| |
| static bool is_pvr_timeline(struct file *file) |
| { |
| return file->f_op == &pvr_sync_fops; |
| } |
| |
| static struct pvr_sync_timeline *pvr_sync_timeline_fget(int fd) |
| { |
| struct file *file = fget(fd); |
| |
| if (!file) |
| return NULL; |
| |
| if (!is_pvr_timeline(file)) { |
| fput(file); |
| return NULL; |
| } |
| |
| return file->private_data; |
| } |
| |
| static void pvr_sync_timeline_fput(struct pvr_sync_timeline *timeline) |
| { |
| fput(timeline->file); |
| } |
| |
| /* ioctl and fops handling */ |
| |
| static int pvr_sync_open(struct inode *inode, struct file *file) |
| { |
| struct pvr_sync_timeline *timeline; |
| char task_comm[TASK_COMM_LEN]; |
| int err = -ENOMEM; |
| |
| get_task_comm(task_comm, current); |
| |
| timeline = kzalloc(sizeof(*timeline), GFP_KERNEL); |
| if (!timeline) |
| goto err_out; |
| |
| strlcpy(timeline->name, task_comm, sizeof(timeline->name)); |
| timeline->file = file; |
| timeline->is_sw = false; |
| |
| file->private_data = timeline; |
| err = 0; |
| err_out: |
| return err; |
| } |
| |
| static int pvr_sync_close(struct inode *inode, struct file *file) |
| { |
| struct pvr_sync_timeline *timeline = file->private_data; |
| |
| if (timeline->sw_fence_timeline) { |
| /* This makes sure any outstanding SW syncs are marked as |
| * complete at timeline close time. Otherwise it'll leak the |
| * timeline (as outstanding fences hold a ref) and possibly |
| * wedge the system if something is waiting on one of those |
| * fences |
| */ |
| pvr_counting_fence_timeline_force_complete( |
| timeline->sw_fence_timeline); |
| pvr_counting_fence_timeline_put(timeline->sw_fence_timeline); |
| } |
| |
| if (timeline->hw_fence_context) { |
| #if defined(NO_HARDWARE) |
| list_del(&timeline->list); |
| #endif |
| pvr_fence_context_destroy(timeline->hw_fence_context); |
| } |
| |
| kfree(timeline); |
| |
| return 0; |
| } |
| |
| enum PVRSRV_ERROR pvr_sync_finalise_fence(PVRSRV_FENCE fence_fd, |
| void *finalise_data) |
| { |
| struct sync_file *sync_file = finalise_data; |
| struct pvr_fence *pvr_fence; |
| |
| if (!sync_file || (fence_fd < 0)) { |
| pr_err(FILE_NAME ": %s: Invalid input fence\n", __func__); |
| return PVRSRV_ERROR_INVALID_PARAMS; |
| } |
| |
| pvr_fence = to_pvr_fence(sync_file->fence); |
| |
| /* pvr fences can be signalled any time after creation */ |
| dma_fence_enable_sw_signaling(&pvr_fence->base); |
| |
| fd_install(fence_fd, sync_file->file); |
| |
| return PVRSRV_OK; |
| } |
| |
| enum PVRSRV_ERROR pvr_sync_create_fence(const char *fence_name, |
| PVRSRV_TIMELINE new_fence_timeline, |
| PSYNC_CHECKPOINT_CONTEXT psSyncCheckpointContext, |
| PVRSRV_FENCE *new_fence, u64 *fence_uid, void **fence_finalise_data, |
| PSYNC_CHECKPOINT *new_checkpoint_handle, void **timeline_update_sync, |
| __u32 *timeline_update_value) |
| { |
| PVRSRV_ERROR err = PVRSRV_OK; |
| PVRSRV_FENCE new_fence_fd = -1; |
| struct pvr_sync_timeline *timeline; |
| struct pvr_fence *pvr_fence; |
| PSYNC_CHECKPOINT checkpoint; |
| struct sync_file *sync_file; |
| |
| if (new_fence_timeline < 0 || !new_fence || !new_checkpoint_handle |
| || !fence_finalise_data) { |
| pr_err(FILE_NAME ": %s: Invalid input params\n", __func__); |
| err = PVRSRV_ERROR_INVALID_PARAMS; |
| goto err_out; |
| } |
| |
| /* We reserve the new fence FD before taking any operations |
| * as we do not want to fail (e.g. run out of FDs) |
| */ |
| new_fence_fd = get_unused_fd_flags(0); |
| if (new_fence_fd < 0) { |
| pr_err(FILE_NAME ": %s: Failed to get fd\n", __func__); |
| err = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_out; |
| } |
| |
| timeline = pvr_sync_timeline_fget(new_fence_timeline); |
| if (!timeline) { |
| pr_err(FILE_NAME ": %s: Failed to open supplied timeline fd (%d)\n", |
| __func__, new_fence_timeline); |
| err = PVRSRV_ERROR_INVALID_PARAMS; |
| goto err_put_fd; |
| } |
| |
| if (timeline->is_sw) { |
| /* This should never happen! */ |
| pr_err(FILE_NAME ": %s: Request to create a pvr fence on sw timeline (%d)\n", |
| __func__, new_fence_timeline); |
| err = PVRSRV_ERROR_INVALID_PARAMS; |
| goto err_put_timeline; |
| } |
| |
| if (!timeline->hw_fence_context) { |
| #if defined(NO_HARDWARE) |
| unsigned long flags; |
| #endif |
| /* First time we use this timeline, so create a context. */ |
| timeline->hw_fence_context = |
| pvr_fence_context_create(pvr_sync_data.dev_cookie, |
| pvr_sync_data.fence_status_wq, |
| timeline->name); |
| if (!timeline->hw_fence_context) { |
| pr_err(FILE_NAME ": %s: Failed to create fence context (%d)\n", |
| __func__, new_fence_timeline); |
| err = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_put_timeline; |
| } |
| #if defined(NO_HARDWARE) |
| /* Add timeline to active list */ |
| INIT_LIST_HEAD(&timeline->list); |
| spin_lock_irqsave(&pvr_sync_data.pvr_timeline_active_list_lock, flags); |
| list_add_tail(&timeline->list, &pvr_sync_data.pvr_timeline_active_list); |
| spin_unlock_irqrestore(&pvr_sync_data.pvr_timeline_active_list_lock, flags); |
| #endif |
| } |
| |
| pvr_fence = pvr_fence_create(timeline->hw_fence_context, new_fence_timeline, |
| fence_name); |
| if (!pvr_fence) { |
| pr_err(FILE_NAME ": %s: Failed to create new pvr_fence\n", |
| __func__); |
| err = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_put_timeline; |
| } |
| |
| checkpoint = pvr_fence_get_checkpoint(pvr_fence); |
| if (!checkpoint) { |
| pr_err(FILE_NAME ": %s: Failed to get fence checkpoint\n", |
| __func__); |
| err = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_destroy_fence; |
| } |
| |
| sync_file = sync_file_create(&pvr_fence->base); |
| if (!sync_file) { |
| pr_err(FILE_NAME ": %s: Failed to create sync_file\n", |
| __func__); |
| err = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_destroy_fence; |
| } |
| strlcpy(sync_file_user_name(sync_file), |
| pvr_fence->name, |
| sizeof(sync_file_user_name(sync_file))); |
| dma_fence_put(&pvr_fence->base); |
| |
| *new_fence = new_fence_fd; |
| *fence_finalise_data = sync_file; |
| *new_checkpoint_handle = checkpoint; |
| *fence_uid = OSGetCurrentClientProcessIDKM(); |
| *fence_uid = (*fence_uid << 32) | (new_fence_fd & U32_MAX); |
| |
| pvr_sync_timeline_fput(timeline); |
| err_out: |
| return err; |
| |
| err_destroy_fence: |
| pvr_fence_destroy(pvr_fence); |
| err_put_timeline: |
| pvr_sync_timeline_fput(timeline); |
| err_put_fd: |
| put_unused_fd(new_fence_fd); |
| *fence_uid = PVRSRV_NO_FENCE; |
| goto err_out; |
| } |
| |
| enum PVRSRV_ERROR pvr_sync_rollback_fence_data(PVRSRV_FENCE fence_to_rollback, |
| void *fence_data_to_rollback) |
| { |
| struct sync_file *sync_file = fence_data_to_rollback; |
| struct pvr_fence *pvr_fence; |
| |
| if (!sync_file || fence_to_rollback < 0) { |
| pr_err(FILE_NAME ": %s: Invalid fence (%d)\n", __func__, |
| fence_to_rollback); |
| return PVRSRV_ERROR_INVALID_PARAMS; |
| } |
| |
| pvr_fence = to_pvr_fence(sync_file->fence); |
| if (!pvr_fence) { |
| pr_err(FILE_NAME |
| ": %s: Non-PVR fence (%p)\n", |
| __func__, sync_file->fence); |
| return PVRSRV_ERROR_INVALID_PARAMS; |
| } |
| |
| fput(sync_file->file); |
| |
| put_unused_fd(fence_to_rollback); |
| |
| return PVRSRV_OK; |
| } |
| |
| enum PVRSRV_ERROR pvr_sync_resolve_fence( |
| PSYNC_CHECKPOINT_CONTEXT psSyncCheckpointContext, |
| PVRSRV_FENCE fence_to_resolve, u32 *nr_checkpoints, |
| PSYNC_CHECKPOINT **checkpoint_handles, u64 *fence_uid) |
| { |
| PSYNC_CHECKPOINT *checkpoints = NULL; |
| unsigned int i, num_fences, num_used_fences = 0; |
| struct dma_fence **fences = NULL; |
| struct dma_fence *fence; |
| PVRSRV_ERROR err = PVRSRV_OK; |
| |
| if (!nr_checkpoints || !checkpoint_handles || !fence_uid) { |
| pr_err(FILE_NAME ": %s: Invalid input checkpoint pointer\n", |
| __func__); |
| err = PVRSRV_ERROR_INVALID_PARAMS; |
| goto err_out; |
| } |
| |
| *nr_checkpoints = 0; |
| *checkpoint_handles = NULL; |
| *fence_uid = 0; |
| |
| if (fence_to_resolve < 0) |
| goto err_out; |
| |
| fence = sync_file_get_fence(fence_to_resolve); |
| if (!fence) { |
| pr_err(FILE_NAME ": %s: Failed to read sync private data for fd %d\n", |
| __func__, fence_to_resolve); |
| err = PVRSRV_ERROR_HANDLE_NOT_FOUND; |
| goto err_out; |
| } |
| |
| if (dma_fence_is_array(fence)) { |
| struct dma_fence_array *array = to_dma_fence_array(fence); |
| |
| if (!array) { |
| pr_err(FILE_NAME ": %s: Failed to resolve fence array %d\n", |
| __func__, fence_to_resolve); |
| err = PVRSRV_ERROR_HANDLE_NOT_FOUND; |
| goto err_put_fence; |
| } |
| fences = array->fences; |
| num_fences = array->num_fences; |
| } else { |
| fences = &fence; |
| num_fences = 1; |
| } |
| |
| checkpoints = kmalloc_array(num_fences, sizeof(PSYNC_CHECKPOINT), |
| GFP_KERNEL); |
| if (!checkpoints) { |
| err = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_put_fence; |
| } |
| for (i = 0; i < num_fences; i++) { |
| /* Only return the checkpoint if the fence is still active. */ |
| if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, |
| &fences[i]->flags)) { |
| struct pvr_fence *pvr_fence = |
| pvr_fence_create_from_fence( |
| pvr_sync_data.foreign_fence_context, |
| fences[i], |
| "foreign"); |
| if (!pvr_fence) { |
| pr_err(FILE_NAME ": %s: Failed to create fence\n", |
| __func__); |
| err = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_free_checkpoints; |
| } |
| checkpoints[num_used_fences] = |
| pvr_fence_get_checkpoint(pvr_fence); |
| SyncCheckpointTakeRef(checkpoints[num_used_fences]); |
| ++num_used_fences; |
| dma_fence_put(&pvr_fence->base); |
| } |
| } |
| /* If we don't return any checkpoints, delete the array because |
| * the caller will not. |
| */ |
| if (num_used_fences == 0) { |
| kfree(checkpoints); |
| checkpoints = NULL; |
| } |
| |
| *checkpoint_handles = checkpoints; |
| *nr_checkpoints = num_used_fences; |
| *fence_uid = OSGetCurrentClientProcessIDKM(); |
| *fence_uid = (*fence_uid << 32) | (fence_to_resolve & U32_MAX); |
| |
| err_put_fence: |
| dma_fence_put(fence); |
| err_out: |
| return err; |
| |
| err_free_checkpoints: |
| for (i = 0; i < num_used_fences; i++) { |
| if (checkpoints[i]) |
| SyncCheckpointDropRef(checkpoints[i]); |
| } |
| kfree(checkpoints); |
| goto err_put_fence; |
| } |
| |
| u32 pvr_sync_dump_info_on_stalled_ufos(u32 nr_ufos, u32 *vaddrs) |
| { |
| return pvr_fence_dump_info_on_stalled_ufos(pvr_sync_data.foreign_fence_context, |
| nr_ufos, |
| vaddrs); |
| } |
| |
| static long pvr_sync_ioctl_rename(struct pvr_sync_timeline *timeline, |
| void __user *user_data) |
| { |
| int err = 0; |
| struct pvr_sync_rename_ioctl_data data; |
| |
| if (!access_ok(VERIFY_READ, user_data, sizeof(data))) { |
| err = -EFAULT; |
| goto err; |
| } |
| |
| if (copy_from_user(&data, user_data, sizeof(data))) { |
| err = -EFAULT; |
| goto err; |
| } |
| |
| data.szName[sizeof(data.szName) - 1] = '\0'; |
| strlcpy(timeline->name, data.szName, sizeof(timeline->name)); |
| if (timeline->hw_fence_context) |
| strlcpy(timeline->hw_fence_context->name, data.szName, |
| sizeof(timeline->hw_fence_context->name)); |
| |
| err: |
| return err; |
| } |
| |
| static long pvr_sync_ioctl_force_sw_only(struct pvr_sync_timeline *timeline, |
| void **private_data) |
| { |
| /* Already in SW mode? */ |
| if (timeline->sw_fence_timeline) |
| return 0; |
| |
| /* Create a sw_sync timeline with the old GPU timeline's name */ |
| timeline->sw_fence_timeline = pvr_counting_fence_timeline_create( |
| pvr_sync_data.dev_cookie, |
| timeline->name); |
| if (!timeline->sw_fence_timeline) |
| return -ENOMEM; |
| |
| timeline->is_sw = true; |
| |
| return 0; |
| } |
| |
| static long pvr_sync_ioctl_sw_create_fence(struct pvr_sync_timeline *timeline, |
| void __user *user_data) |
| { |
| struct pvr_sw_sync_create_fence_data data; |
| struct sync_file *sync_file; |
| int fd = get_unused_fd_flags(0); |
| struct dma_fence *fence; |
| int err = -EFAULT; |
| |
| if (fd < 0) { |
| pr_err(FILE_NAME ": %s: Failed to find unused fd (%d)\n", |
| __func__, fd); |
| goto err_out; |
| } |
| |
| if (copy_from_user(&data, user_data, sizeof(data))) { |
| pr_err(FILE_NAME ": %s: Failed copy from user\n", __func__); |
| goto err_put_fd; |
| } |
| |
| fence = pvr_counting_fence_create(timeline->sw_fence_timeline, |
| data.value); |
| if (!fence) { |
| pr_err(FILE_NAME ": %s: Failed to create a sync point (%d)\n", |
| __func__, fd); |
| err = -ENOMEM; |
| goto err_put_fd; |
| } |
| |
| sync_file = sync_file_create(fence); |
| if (!sync_file) { |
| pr_err(FILE_NAME ": %s: Failed to create a sync point (%d)\n", |
| __func__, fd); |
| err = -ENOMEM; |
| goto err_put_fence; |
| } |
| |
| data.fence = fd; |
| |
| if (copy_to_user(user_data, &data, sizeof(data))) { |
| pr_err(FILE_NAME ": %s: Failed copy to user\n", __func__); |
| goto err_put_fence; |
| } |
| |
| fd_install(fd, sync_file->file); |
| err = 0; |
| |
| dma_fence_put(fence); |
| err_out: |
| return err; |
| |
| err_put_fence: |
| dma_fence_put(fence); |
| err_put_fd: |
| put_unused_fd(fd); |
| goto err_out; |
| } |
| |
| static long pvr_sync_ioctl_sw_inc(struct pvr_sync_timeline *timeline, |
| void __user *user_data) |
| { |
| u32 value; |
| bool res; |
| |
| if (copy_from_user(&value, user_data, sizeof(value))) |
| return -EFAULT; |
| |
| res = pvr_counting_fence_timeline_inc(timeline->sw_fence_timeline, value); |
| |
| /* pvr_counting_fence_timeline_inc won't allow sw timeline to be |
| * advanced beyond the last defined point |
| */ |
| if (!res) { |
| pr_err("pvr_sync_file: attempt to advance SW timeline beyond last defined point\n"); |
| return -EPERM; |
| } |
| |
| return 0; |
| } |
| |
| static long |
| pvr_sync_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| void __user *user_data = (void __user *)arg; |
| long err = -ENOTTY; |
| struct pvr_sync_timeline *timeline = file->private_data; |
| |
| if (!timeline->is_sw) { |
| |
| switch (cmd) { |
| case PVR_SYNC_IOC_RENAME: |
| err = pvr_sync_ioctl_rename(timeline, user_data); |
| break; |
| case PVR_SYNC_IOC_FORCE_SW_ONLY: |
| err = pvr_sync_ioctl_force_sw_only(timeline, |
| &file->private_data); |
| break; |
| default: |
| break; |
| } |
| } else { |
| |
| switch (cmd) { |
| case PVR_SW_SYNC_IOC_CREATE_FENCE: |
| err = pvr_sync_ioctl_sw_create_fence(timeline, |
| user_data); |
| break; |
| case PVR_SW_SYNC_IOC_INC: |
| err = pvr_sync_ioctl_sw_inc(timeline, user_data); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return err; |
| } |
| |
| static const struct file_operations pvr_sync_fops = { |
| .owner = THIS_MODULE, |
| .open = pvr_sync_open, |
| .release = pvr_sync_close, |
| .unlocked_ioctl = pvr_sync_ioctl, |
| .compat_ioctl = pvr_sync_ioctl, |
| }; |
| |
| static struct miscdevice pvr_sync_device = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = PVRSYNC_MODNAME, |
| .fops = &pvr_sync_fops, |
| }; |
| |
| static void |
| pvr_sync_debug_request_heading(void *data, u32 verbosity, |
| DUMPDEBUG_PRINTF_FUNC *pfnDumpDebugPrintf, |
| void *pvDumpDebugFile) |
| { |
| if (verbosity == DEBUG_REQUEST_VERBOSITY_MEDIUM) |
| PVR_DUMPDEBUG_LOG(pfnDumpDebugPrintf, pvDumpDebugFile, "------[ Native Fence Sync: timelines ]------"); |
| } |
| |
| enum PVRSRV_ERROR pvr_sync_init(struct device *dev) |
| { |
| struct drm_device *ddev = dev_get_drvdata(dev); |
| struct pvr_drm_private *priv = ddev->dev_private; |
| enum PVRSRV_ERROR error; |
| int err; |
| |
| error = PVRSRVRegisterDbgRequestNotify(&pvr_sync_data.dbg_request_handle, |
| priv->dev_node, |
| pvr_sync_debug_request_heading, |
| DEBUG_REQUEST_LINUXFENCE, |
| NULL); |
| if (error != PVRSRV_OK) { |
| pr_err("%s: failed to register debug request callback (%s)\n", |
| __func__, PVRSRVGetErrorStringKM(error)); |
| goto err_out; |
| } |
| |
| pvr_sync_data.dev_cookie = priv->dev_node; |
| pvr_sync_data.fence_status_wq = priv->fence_status_wq; |
| |
| pvr_sync_data.foreign_fence_context = |
| pvr_global_fence_context_create(pvr_sync_data.dev_cookie, |
| pvr_sync_data.fence_status_wq, |
| "foreign_sync"); |
| if (!pvr_sync_data.foreign_fence_context) { |
| pr_err(FILE_NAME ": %s: Failed to create foreign sync context\n", |
| __func__); |
| error = PVRSRV_ERROR_OUT_OF_MEMORY; |
| goto err_out; |
| } |
| |
| #if defined(NO_HARDWARE) |
| INIT_LIST_HEAD(&pvr_sync_data.pvr_timeline_active_list); |
| #endif |
| |
| /* Register the resolve fence and create fence functions with |
| * sync_checkpoint.c |
| * The pvr_fence context registers its own EventObject callback to |
| * update sync status |
| */ |
| SyncCheckpointRegisterFunctions(pvr_sync_resolve_fence, |
| pvr_sync_create_fence, pvr_sync_rollback_fence_data, |
| pvr_sync_finalise_fence, |
| #if defined(NO_HARDWARE) |
| pvr_sync_nohw_signal_fence, |
| #else |
| NULL, |
| #endif |
| pvr_sync_free_checkpoint_list_mem, |
| pvr_sync_dump_info_on_stalled_ufos); |
| |
| err = misc_register(&pvr_sync_device); |
| if (err) { |
| pr_err(FILE_NAME ": %s: Failed to register pvr_sync device (%d)\n", |
| __func__, err); |
| error = PVRSRV_ERROR_RESOURCE_UNAVAILABLE; |
| goto err_unregister_checkpoint_funcs; |
| } |
| error = PVRSRV_OK; |
| |
| err_out: |
| return error; |
| |
| err_unregister_checkpoint_funcs: |
| SyncCheckpointRegisterFunctions(NULL, NULL, NULL, NULL, NULL, NULL, NULL); |
| pvr_fence_context_destroy(pvr_sync_data.foreign_fence_context); |
| goto err_out; |
| } |
| |
| void pvr_sync_deinit(void) |
| { |
| SyncCheckpointRegisterFunctions(NULL, NULL, NULL, NULL, NULL, NULL, NULL); |
| misc_deregister(&pvr_sync_device); |
| pvr_fence_context_destroy(pvr_sync_data.foreign_fence_context); |
| PVRSRVUnregisterDbgRequestNotify(pvr_sync_data.dbg_request_handle); |
| } |
| |
| struct pvr_counting_fence_timeline *pvr_sync_get_sw_timeline(int fd) |
| { |
| struct pvr_sync_timeline *timeline; |
| struct pvr_counting_fence_timeline *sw_timeline = NULL; |
| |
| timeline = pvr_sync_timeline_fget(fd); |
| if (!timeline) |
| return NULL; |
| |
| sw_timeline = |
| pvr_counting_fence_timeline_get(timeline->sw_fence_timeline); |
| |
| pvr_sync_timeline_fput(timeline); |
| return sw_timeline; |
| } |