blob: 7182a61c26e5cb05b778ee743f0f4dd55a6480cc [file] [log] [blame]
/*************************************************************************/ /*!
@File pvr_sync.c
@Title Kernel driver for Android's sync mechanism
@Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@License Dual MIT/GPLv2
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.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 ("GPL") in which case the provisions
of GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms of
GPL, and not to allow others to use your version of this file under the terms
of the MIT license, indicate your decision by deleting the provisions above
and replace them with the notice and other provisions required by GPL as set
out in the file called "GPL-COPYING" included in this distribution. If you do
not delete the provisions above, a recipient may use your version of this file
under the terms of either the MIT license or GPL.
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.
*/ /**************************************************************************/
/* vi: set ts=8: */
#include "pvr_sync.h"
#include "pvr_fd_sync_kernel.h"
#include "services_kernel_client.h"
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <linux/syscalls.h>
#include <linux/miscdevice.h>
#include <linux/anon_inodes.h>
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0))
#include <linux/sync.h>
#ifndef CONFIG_SW_SYNC_USER
#include <linux/sw_sync.h>
#endif
#else
#include <../drivers/staging/android/sync.h>
#ifndef CONFIG_SW_SYNC_USER
#include <../drivers/staging/android/sw_sync.h>
#endif
#endif
/*
* get_unused_fd was first removed from vanilla kernels in 3.19 and
* get_unused_fd_flags was first exported from vanilla kernels in 3.7.
*/
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0))
#define get_unused_fd() get_unused_fd_flags(0)
#endif
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0))
static inline struct sync_timeline *sync_pt_parent(struct sync_pt *pt)
{
return pt->parent;
}
static inline int sync_pt_get_status(struct sync_pt *pt)
{
return pt->status;
}
#define for_each_sync_pt(s, f, c) \
list_for_each_entry((s), &(f)->pt_list_head, pt_list)
#else /* (LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)) */
static inline int sync_pt_get_status(struct sync_pt *pt)
{
/* No error state for raw dma-buf fences */
return fence_is_signaled(&pt->base) ? 1 : 0;
}
#define for_each_sync_pt(s, f, c) \
for ((c) = 0, (s) = (struct sync_pt *)(f)->cbs[0].sync_pt; \
(c) < (f)->num_fences; \
(c)++, (s) = (struct sync_pt *)(f)->cbs[c].sync_pt)
#endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0)) */
/* #define DEBUG_OUTPUT 1 */
#ifdef DEBUG_OUTPUT
#define DPF(fmt, ...) pr_err("pvr_sync: " fmt "\n", __VA_ARGS__)
#else
#define DPF(fmt, ...) do {} while (0)
#endif
#define PVR_DUMPDEBUG_LOG(pfnDumpDebugPrintf, pvDumpDebugFile, fmt, ...) \
do { \
if (pfnDumpDebugPrintf) { \
pfnDumpDebugPrintf(pvDumpDebugFile, fmt, __VA_ARGS__); \
} else { \
pr_info("pvr_sync: " fmt, __VA_ARGS__); \
} \
} while (0)
#define SYNC_MAX_POOL_SIZE 10
enum {
SYNC_TL_TYPE = 0,
SYNC_PT_FENCE_TYPE = 1,
SYNC_PT_CLEANUP_TYPE = 2,
SYNC_PT_FOREIGN_FENCE_TYPE = 3,
SYNC_PT_FOREIGN_CLEANUP_TYPE = 4,
};
struct pvr_sync_append_data {
u32 nr_updates;
PRGXFWIF_UFO_ADDR *update_ufo_addresses;
u32 *update_values;
u32 nr_checks;
PRGXFWIF_UFO_ADDR *check_ufo_addresses;
u32 *check_values;
/* The cleanup list is needed for rollback (as that's the only op
* taken).
*/
u32 nr_cleanup_syncs;
struct pvr_sync_native_sync_prim **cleanup_syncs;
/* A FD is reserved in append_fences, but is not associated with
* the update fence until pvr_sync_get_update_fd().
*/
int update_fence_fd;
/* Keep the sync points around for fput and if rollback is needed */
struct sync_fence *update_fence;
struct pvr_sync_native_sync_prim *update_sync;
struct pvr_sync_native_sync_prim *update_timeline_sync;
struct sync_fence *check_fence;
};
/* Services client sync prim wrapper. This is used to hold debug information
* and make it possible to cache unused syncs.
*/
struct pvr_sync_native_sync_prim {
/* List for the sync pool support. */
struct list_head list;
/* Base services sync prim structure */
struct PVRSRV_CLIENT_SYNC_PRIM *client_sync;
/* The next queued value which should be used */
u32 next_value;
/* Every sync data will get some unique id */
u32 id;
/* FWAddr used by the client sync */
u32 vaddr;
/* The type this sync is used for in our driver. Used in
* pvr_sync_debug_request().
*/
u8 type;
/* A debug class name also printed in pvr_sync_debug_request(). */
char class[32];
/* List for the cleanup syncs attached to a sync_pt */
struct list_head cleanup_list;
};
/* 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 {
/* Back reference to the sync_timeline. Not always valid */
struct sync_timeline *obj;
/* Global timeline list support */
struct list_head list;
/* Timeline sync */
struct pvr_sync_kernel_pair *kernel;
/* Reference count for this object */
struct kref kref;
/* Used only by pvr_sync_update_all_timelines(). False if the timeline
* has been detected as racing with pvr_sync_destroy_timeline().
*/
bool valid;
};
/* This is the IMG extension of a sync_timeline */
struct pvr_sync_timeline_wrapper {
/* Original timeline struct. Needs to come first. */
struct sync_timeline obj;
/* Pointer to extra timeline data. Separated life-cycle. */
struct pvr_sync_timeline *timeline;
};
struct pvr_sync_kernel_pair {
/* Binary sync point representing the android native sync in hw. */
struct pvr_sync_native_sync_prim *fence_sync;
/* Cleanup sync list. If the base sync prim is used for "checking"
* only within a GL stream, there is no way of knowing when this has
* happened. So each check appends another sync prim just used for
* update at the end of the command, so we know if all syncs in this
* cleanup list are complete there are no outstanding renders waiting
* to check this, so it can safely be freed.
*/
struct list_head cleanup_sync_list;
/* A temporary pointer used to track the 'new' cleanup_sync added to
* cleanup_sync_list within pvr_sync_append_fences()
*/
struct pvr_sync_native_sync_prim *current_cleanup_sync;
/* Sync points can go away when there are deferred hardware operations
* still outstanding. We must not free the SERVER_SYNC_PRIMITIVE until
* the hardware is finished, so we add it to a defer list which is
* processed periodically ("defer-free").
*
* Note that the defer-free list is global, not per-timeline.
*/
struct list_head list;
};
struct pvr_sync_data {
/* Every sync point has a services sync object. This object is used
* by the hardware to enforce ordering -- it is attached as a source
* dependency to various commands.
*/
struct pvr_sync_kernel_pair *kernel;
/* The timeline update value for this sync point. */
u32 timeline_update_value;
/* This refcount is incremented at create and dup time, and decremented
* at free time. It ensures the object doesn't start the defer-free
* process until it is no longer referenced.
*/
struct kref kref;
};
/* This is the IMG extension of a sync_pt */
struct pvr_sync_pt {
/* Original sync_pt structure. Needs to come first. */
struct sync_pt pt;
/* Private shared data */
struct pvr_sync_data *sync_data;
};
/* This is the IMG extension of a sync_fence */
struct pvr_sync_fence {
/* Original sync_fence structure. Needs to come first. */
struct sync_fence *fence;
/* To ensure callbacks are always received for fences / sync_pts, even
* after the fence has been 'put' (freed), we must take a reference to
* the fence. We still need to 'put' the fence ourselves, but this might
* happen in irq context, where fput() is not allowed (in kernels <3.6).
* We must add the fence to a list which is processed in WQ context.
*/
struct list_head list;
};
/* Any sync point from a foreign (non-PVR) timeline needs to have a "shadow"
* sync prim. This is modelled as a software operation. The foreign driver
* completes the operation by calling a callback we registered with it.
*/
struct pvr_sync_fence_waiter {
/* Base sync driver waiter structure */
struct sync_fence_waiter waiter;
/* "Shadow" sync prim backing the foreign driver's sync_pt */
struct pvr_sync_kernel_pair *kernel;
/* Optimizes lookup of fence for defer-put operation */
struct pvr_sync_fence *sync_fence;
};
/* Global data for the sync driver */
static struct {
/* Services connection */
void *device_cookie;
/* Complete notify handle */
void *command_complete_handle;
/* Defer-free workqueue. Syncs may still be in use by the HW when freed,
* so we have to keep them around until the HW is done with them at
* some later time. This workqueue iterates over the list of free'd
* syncs, checks if they are in use, and frees the sync device memory
* when done with.
*/
struct workqueue_struct *defer_free_wq;
struct work_struct defer_free_work;
/* check_status workqueue: When a foreign point is completed, a SW
* operation marks the sync as completed to allow the operations to
* continue. This completion may require the hardware to be notified,
* which may be expensive/take locks, so we push that to a workqueue
*/
struct workqueue_struct *check_status_wq;
struct work_struct check_status_work;
/* Context used to create client sync prims. */
struct SYNC_PRIM_CONTEXT *sync_prim_context;
/* Debug notify handle */
void *debug_notify_handle;
/* Unique id counter for the sync prims */
atomic_t sync_id;
/* The global event object (used to wait between checks for
* deferred-free sync status).
*/
void *event_object_handle;
} pvr_sync_data;
/* List of timelines created by this driver */
static LIST_HEAD(timeline_list);
static DEFINE_MUTEX(timeline_list_mutex);
/* Sync pool support */
static LIST_HEAD(sync_pool_free_list);
static LIST_HEAD(sync_pool_active_list);
static DEFINE_MUTEX(sync_pool_mutex);
static s32 sync_pool_size;
static u32 sync_pool_created;
static u32 sync_pool_reused;
/* The "defer-free" object list. Driver global. */
static LIST_HEAD(sync_prim_free_list);
static DEFINE_SPINLOCK(sync_prim_free_list_spinlock);
/* The "defer-put" object list. Driver global. */
static LIST_HEAD(sync_fence_put_list);
static DEFINE_SPINLOCK(sync_fence_put_list_spinlock);
static inline void set_sync_value(struct pvr_sync_native_sync_prim *sync,
u32 value)
{
*(sync->client_sync->pui32LinAddr) = value;
}
static inline u32 get_sync_value(struct pvr_sync_native_sync_prim *sync)
{
return *(sync->client_sync->pui32LinAddr);
}
static inline void complete_sync(struct pvr_sync_native_sync_prim *sync)
{
*(sync->client_sync->pui32LinAddr) = sync->next_value;
}
static inline int is_sync_met(struct pvr_sync_native_sync_prim *sync)
{
return *(sync->client_sync->pui32LinAddr) == sync->next_value;
}
static inline struct pvr_sync_timeline *get_timeline(struct sync_timeline *obj)
{
return ((struct pvr_sync_timeline_wrapper *)obj)->timeline;
}
static inline struct pvr_sync_timeline *get_timeline_pt(struct sync_pt *pt)
{
return get_timeline(sync_pt_parent(pt));
}
static inline int
pvr_sync_has_kernel_signaled(struct pvr_sync_kernel_pair *kernel)
{
/* Idle syncs are always signaled */
if (!kernel)
return 1;
return is_sync_met(kernel->fence_sync);
}
#ifdef DEBUG_OUTPUT
static char *debug_info_timeline(struct pvr_sync_timeline *timeline)
{
static char info[256];
snprintf(info, sizeof(info),
"n='%s' id=%u fw=0x%x tl_curr=%u tl_next=%u",
timeline->obj ? timeline->obj->name : "?",
timeline->kernel->fence_sync->id,
timeline->kernel->fence_sync->vaddr,
get_sync_value(timeline->kernel->fence_sync),
timeline->kernel->fence_sync->next_value);
return info;
}
static char *debug_info_sync_pt(struct sync_pt *pt)
{
struct pvr_sync_timeline *timeline = get_timeline_pt(pt);
struct pvr_sync_pt *pvr_pt = (struct pvr_sync_pt *)pt;
struct pvr_sync_kernel_pair *kernel = pvr_pt->sync_data->kernel;
static char info[256], info1[256];
if (kernel) {
unsigned int cleanup_count = 0;
unsigned int info1_pos = 0;
struct list_head *pos;
info1[0] = 0;
list_for_each(pos, &kernel->cleanup_sync_list) {
struct pvr_sync_native_sync_prim *cleanup_sync =
list_entry(pos,
struct pvr_sync_native_sync_prim,
cleanup_list);
int string_size = 0;
string_size = snprintf(info1 + info1_pos,
sizeof(info1) - info1_pos,
" # cleanup %u: id=%u fw=0x%x curr=%u next=%u",
cleanup_count,
cleanup_sync->id,
cleanup_sync->vaddr,
get_sync_value(cleanup_sync),
cleanup_sync->next_value);
cleanup_count++;
info1_pos += string_size;
/* Truncate the string and stop if we run out of space
* This should stop any underflow of snprintf's 'size'
* arg too
*/
if (info1_pos >= sizeof(info1))
break;
}
snprintf(info, sizeof(info),
"status=%d tl_taken=%u ref=%d # sync: id=%u fw=0x%x curr=%u next=%u%s # tl: %s",
pvr_sync_has_kernel_signaled(kernel),
pvr_pt->sync_data->timeline_update_value,
atomic_read(&pvr_pt->sync_data->kref.refcount),
kernel->fence_sync->id,
kernel->fence_sync->vaddr,
get_sync_value(kernel->fence_sync),
kernel->fence_sync->next_value,
info1, debug_info_timeline(timeline));
} else {
snprintf(info, sizeof(info),
"status=%d tl_taken=%u ref=%d # sync: idle # tl: %s",
pvr_sync_has_kernel_signaled(kernel),
pvr_pt->sync_data->timeline_update_value,
atomic_read(&pvr_pt->sync_data->kref.refcount),
debug_info_timeline(timeline));
}
return info;
}
#endif /* DEBUG_OUTPUT */
static enum PVRSRV_ERROR
sync_pool_get(struct pvr_sync_native_sync_prim **_sync,
const char *class_name, u8 type)
{
struct pvr_sync_native_sync_prim *sync;
enum PVRSRV_ERROR error = PVRSRV_OK;
mutex_lock(&sync_pool_mutex);
if (list_empty(&sync_pool_free_list)) {
/* If there is nothing in the pool, create a new sync prim. */
sync = kmalloc(sizeof(struct pvr_sync_native_sync_prim),
GFP_KERNEL);
if (!sync) {
pr_err("pvr_sync: %s: Failed to allocate sync data\n",
__func__);
error = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_unlock;
}
error = SyncPrimAlloc(pvr_sync_data.sync_prim_context,
&sync->client_sync, class_name);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to allocate sync prim (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_free;
}
sync->vaddr = SyncPrimGetFirmwareAddr(sync->client_sync);
list_add_tail(&sync->list, &sync_pool_active_list);
++sync_pool_created;
} else {
sync = list_first_entry(&sync_pool_free_list,
struct pvr_sync_native_sync_prim, list);
list_move_tail(&sync->list, &sync_pool_active_list);
--sync_pool_size;
++sync_pool_reused;
}
sync->id = atomic_inc_return(&pvr_sync_data.sync_id);
sync->type = type;
strncpy(sync->class, class_name, sizeof(sync->class));
/* make sure string is null terminated */
sync->class[sizeof(sync->class) - 1] = '\0';
/* Its crucial to reset the sync to zero */
set_sync_value(sync, 0);
sync->next_value = 0;
*_sync = sync;
err_unlock:
mutex_unlock(&sync_pool_mutex);
return error;
err_free:
kfree(sync);
goto err_unlock;
}
static void sync_pool_put(struct pvr_sync_native_sync_prim *sync)
{
mutex_lock(&sync_pool_mutex);
if (sync_pool_size < SYNC_MAX_POOL_SIZE) {
/* Mark it as unused */
set_sync_value(sync, 0xffffffff);
list_move(&sync->list, &sync_pool_free_list);
++sync_pool_size;
} else {
/* Mark it as invalid */
set_sync_value(sync, 0xdeadbeef);
list_del(&sync->list);
SyncPrimFree(sync->client_sync);
kfree(sync);
}
mutex_unlock(&sync_pool_mutex);
}
static void sync_pool_clear(void)
{
struct pvr_sync_native_sync_prim *sync, *n;
mutex_lock(&sync_pool_mutex);
list_for_each_entry_safe(sync, n, &sync_pool_free_list, list) {
/* Mark it as invalid */
set_sync_value(sync, 0xdeadbeef);
list_del(&sync->list);
SyncPrimFree(sync->client_sync);
kfree(sync);
--sync_pool_size;
}
mutex_unlock(&sync_pool_mutex);
}
static void pvr_sync_debug_request(void *hDebugRequestHandle,
u32 ui32VerbLevel,
DUMPDEBUG_PRINTF_FUNC *pfnDumpDebugPrintf,
void *pvDumpDebugFile)
{
struct pvr_sync_native_sync_prim *sync;
static const char *const type_names[] = {
"Timeline", "Fence", "Cleanup",
"Foreign Fence", "Foreign Cleanup"
};
if (ui32VerbLevel == DEBUG_REQUEST_VERBOSITY_HIGH) {
mutex_lock(&sync_pool_mutex);
PVR_DUMPDEBUG_LOG(pfnDumpDebugPrintf, pvDumpDebugFile,
"Dumping all pending android native syncs (Pool usage: %d%% - %d %d)",
sync_pool_reused ?
(10000 /
((sync_pool_created + sync_pool_reused) *
100 / sync_pool_reused)) : 0,
sync_pool_created, sync_pool_reused);
list_for_each_entry(sync, &sync_pool_active_list, list) {
if (is_sync_met(sync))
continue;
BUG_ON(sync->type >= ARRAY_SIZE(type_names));
PVR_DUMPDEBUG_LOG(pfnDumpDebugPrintf, pvDumpDebugFile,
"\tID = %d, FWAddr = 0x%08x: Current = 0x%08x, Next = 0x%08x, %s (%s)",
sync->id, sync->vaddr,
get_sync_value(sync),
sync->next_value,
sync->class,
type_names[sync->type]);
}
#if 0
PVR_DUMPDEBUG_LOG(g_pfnDumpDebugPrintf,
"Dumping all unused syncs");
list_for_each_entry(sync, &sync_pool_free_list, list) {
BUG_ON(sync->type >= ARRAY_SIZE(type_names));
PVR_DUMPDEBUG_LOG(g_pfnDumpDebugPrintf,
"\tID = %d, FWAddr = 0x%08x: Current = 0x%08x, Next = 0x%08x, %s (%s)",
sync->id, sync->vaddr,
get_sync_value(sync),
sync->next_value,
sync->class,
type_names[sync->type]);
}
#endif
mutex_unlock(&sync_pool_mutex);
}
}
static struct sync_pt *pvr_sync_dup(struct sync_pt *sync_pt)
{
struct pvr_sync_pt *pvr_pt_a = (struct pvr_sync_pt *)sync_pt;
struct pvr_sync_pt *pvr_pt_b = NULL;
DPF("%s: # %s", __func__, debug_info_sync_pt(sync_pt));
pvr_pt_b = (struct pvr_sync_pt *)
sync_pt_create(sync_pt_parent(sync_pt),
sizeof(struct pvr_sync_pt));
if (!pvr_pt_b) {
pr_err("pvr_sync: %s: Failed to dup sync pt\n", __func__);
goto err_out;
}
kref_get(&pvr_pt_a->sync_data->kref);
pvr_pt_b->sync_data = pvr_pt_a->sync_data;
err_out:
return (struct sync_pt *)pvr_pt_b;
}
static int pvr_sync_has_signaled(struct sync_pt *sync_pt)
{
struct pvr_sync_pt *pvr_pt = (struct pvr_sync_pt *)sync_pt;
DPF("%s: # %s", __func__, debug_info_sync_pt(sync_pt));
return pvr_sync_has_kernel_signaled(pvr_pt->sync_data->kernel);
}
static int pvr_sync_compare(struct sync_pt *a, struct sync_pt *b)
{
u32 a1 = ((struct pvr_sync_pt *)a)->sync_data->timeline_update_value;
u32 b1 = ((struct pvr_sync_pt *)b)->sync_data->timeline_update_value;
DPF("%s: a # %s", __func__, debug_info_sync_pt(a));
DPF("%s: b # %s", __func__, debug_info_sync_pt(b));
if (a1 == b1)
return 0;
/* Take integer wrapping into account */
return ((s32)a1 - (s32)b1) < 0 ? -1 : 1;
}
static void wait_for_sync(struct pvr_sync_native_sync_prim *sync)
{
#ifndef NO_HARDWARE
void *event_object = NULL;
enum PVRSRV_ERROR error = PVRSRV_OK;
while (sync && !is_sync_met(sync)) {
if (!event_object) {
error = OSEventObjectOpen(
pvr_sync_data.event_object_handle,
&event_object);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Error opening event object (%s)\n",
__func__,
PVRSRVGetErrorStringKM(error));
break;
}
}
error = OSEventObjectWait(event_object);
if (error != PVRSRV_OK && error != PVRSRV_ERROR_TIMEOUT) {
pr_err("pvr_sync: %s: Error waiting on event object (%s)\n",
__func__,
PVRSRVGetErrorStringKM(error));
}
}
if (event_object)
OSEventObjectClose(event_object);
#endif /* NO_HARDWARE */
}
static void pvr_sync_defer_free(struct pvr_sync_kernel_pair *kernel)
{
unsigned long flags;
spin_lock_irqsave(&sync_prim_free_list_spinlock, flags);
list_add_tail(&kernel->list, &sync_prim_free_list);
spin_unlock_irqrestore(&sync_prim_free_list_spinlock, flags);
queue_work(pvr_sync_data.defer_free_wq, &pvr_sync_data.defer_free_work);
}
/* This function assumes the timeline_list_mutex is held while it runs */
static void pvr_sync_destroy_timeline_locked(struct kref *kref)
{
struct pvr_sync_timeline *timeline = (struct pvr_sync_timeline *)
container_of(kref, struct pvr_sync_timeline, kref);
pvr_sync_defer_free(timeline->kernel);
list_del(&timeline->list);
kfree(timeline);
}
static void pvr_sync_destroy_timeline(struct kref *kref)
{
mutex_lock(&timeline_list_mutex);
pvr_sync_destroy_timeline_locked(kref);
mutex_unlock(&timeline_list_mutex);
}
static void pvr_sync_release_timeline(struct sync_timeline *obj)
{
struct pvr_sync_timeline *timeline = get_timeline(obj);
/* If pvr_sync_open failed after calling sync_timeline_create, this
* can be called with a timeline that has not got a timeline sync
* or been added to our timeline list. Use a NULL timeline to
* detect and handle this condition
*/
if (!timeline)
return;
DPF("%s: # %s", __func__, debug_info_timeline(timeline));
wait_for_sync(timeline->kernel->fence_sync);
/* Whether or not we're the last reference, obj is going away
* after this function returns, so remove our back reference
* to it.
*/
timeline->obj = NULL;
/* This might be the last reference to the timeline object.
* If so, we'll go ahead and delete it now.
*/
kref_put(&timeline->kref, pvr_sync_destroy_timeline);
}
/* The print_obj() and print_pt() functions have been removed, so we're forced
* to use the timeline_value_str() and pt_value_str() functions. These are
* worse because we're limited to 64 characters, and the strings for sync
* pts have to be formatted like:
*
* pt active: pt_info / tl_info
*
* For us, the tl_info is complicated and doesn't need to be repeated over
* and over. So try to detect the way sync_print_pt() calls the two value_str
* functions and change what pvr_sync_timeline_value_str() returns dynamically.
*/
static struct sync_timeline *last_pt_timeline;
static void pvr_sync_timeline_value_str(struct sync_timeline *sync_timeline,
char *str, int size)
{
struct pvr_sync_timeline *timeline = get_timeline(sync_timeline);
if (sync_timeline != last_pt_timeline) {
snprintf(str, size, "%u 0x%x %u/%u",
timeline->kernel->fence_sync->id,
timeline->kernel->fence_sync->vaddr,
get_sync_value(timeline->kernel->fence_sync),
timeline->kernel->fence_sync->next_value);
} else {
snprintf(str, size, "%u",
get_sync_value(timeline->kernel->fence_sync));
}
}
static void pvr_sync_pt_value_str(struct sync_pt *sync_pt, char *str, int size)
{
struct pvr_sync_pt *pvr_pt = (struct pvr_sync_pt *)sync_pt;
struct pvr_sync_kernel_pair *kernel;
if (!pvr_pt->sync_data)
return;
kernel = pvr_pt->sync_data->kernel;
/* Messages must be at most 64 bytes (including the null terminator):
*
* 123456789012345678901234567890123456789012345678901234567890123
*
* ID FW ADDR C/N # REF TAKEN CLEANUP_COUNT
* 123456 0xdeadbeef 0/1 # r=2 123456 1
*/
if (kernel) {
unsigned int cleanup_count = 0;
struct list_head *pos;
list_for_each(pos, &kernel->cleanup_sync_list) {
cleanup_count++;
}
snprintf(str, size,
"%u 0x%x %u/%u r=%d %u %u",
kernel->fence_sync->id,
kernel->fence_sync->vaddr,
get_sync_value(kernel->fence_sync),
kernel->fence_sync->next_value,
atomic_read(&pvr_pt->sync_data->kref.refcount),
pvr_pt->sync_data->timeline_update_value,
cleanup_count);
} else {
snprintf(str, size, "idle # r=%d %u",
atomic_read(&pvr_pt->sync_data->kref.refcount),
pvr_pt->sync_data->timeline_update_value);
}
last_pt_timeline = sync_pt_parent(sync_pt);
}
/* pvr_sync_create_sync_data() should be called with the bridge lock held */
static struct pvr_sync_data *
pvr_sync_create_sync_data(struct sync_timeline *obj)
{
struct pvr_sync_data *sync_data = NULL;
enum PVRSRV_ERROR error;
sync_data = kzalloc(sizeof(struct pvr_sync_data), GFP_KERNEL);
if (!sync_data)
goto err_out;
kref_init(&sync_data->kref);
sync_data->kernel =
kzalloc(sizeof(struct pvr_sync_kernel_pair),
GFP_KERNEL);
if (!sync_data->kernel)
goto err_free_data;
INIT_LIST_HEAD(&sync_data->kernel->cleanup_sync_list);
error = sync_pool_get(&sync_data->kernel->fence_sync,
obj->name, SYNC_PT_FENCE_TYPE);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to allocate sync prim (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_free_kernel;
}
err_out:
return sync_data;
err_free_kernel:
kfree(sync_data->kernel);
err_free_data:
kfree(sync_data);
sync_data = NULL;
goto err_out;
}
static void pvr_sync_free_sync_data(struct kref *kref)
{
struct pvr_sync_data *sync_data = (struct pvr_sync_data *)
container_of(kref, struct pvr_sync_data, kref);
if (sync_data->kernel)
pvr_sync_defer_free(sync_data->kernel);
kfree(sync_data);
}
static void pvr_sync_free_sync(struct sync_pt *sync_pt)
{
struct pvr_sync_pt *pvr_pt = (struct pvr_sync_pt *)sync_pt;
DPF("%s: # %s", __func__, debug_info_sync_pt(sync_pt));
kref_put(&pvr_pt->sync_data->kref, pvr_sync_free_sync_data);
}
/* this function uses pvr_sync_timeline_ops defined below */
static int pvr_sync_fill_driver_data(struct sync_pt *, void *, int);
static struct sync_timeline_ops pvr_sync_timeline_ops = {
.driver_name = PVRSYNC_MODNAME,
.dup = pvr_sync_dup,
.has_signaled = pvr_sync_has_signaled,
.compare = pvr_sync_compare,
.free_pt = pvr_sync_free_sync,
.release_obj = pvr_sync_release_timeline,
.timeline_value_str = pvr_sync_timeline_value_str,
.pt_value_str = pvr_sync_pt_value_str,
.fill_driver_data = pvr_sync_fill_driver_data,
};
static inline bool is_pvr_timeline(struct sync_timeline *obj)
{
return obj->ops == &pvr_sync_timeline_ops;
}
static inline bool is_pvr_timeline_pt(struct sync_pt *pt)
{
return is_pvr_timeline(sync_pt_parent(pt));
}
static int
pvr_sync_fill_driver_data(struct sync_pt *sync_pt, void *data, int size)
{
struct pvr_sync_pt_info *info = (struct pvr_sync_pt_info *)data;
struct pvr_sync_pt *pvr_pt = (struct pvr_sync_pt *)sync_pt;
struct pvr_sync_data *sync_data = pvr_pt->sync_data;
struct pvr_sync_kernel_pair *kernel = sync_data->kernel;
if (size < sizeof(struct pvr_sync_pt_info))
return -ENOMEM;
info->ui32TlTaken = sync_data->timeline_update_value;
if (kernel) {
info->id = kernel->fence_sync->id;
info->ui32FWAddr = kernel->fence_sync->vaddr;
info->ui32CurrOp = get_sync_value(kernel->fence_sync);
info->ui32NextOp = kernel->fence_sync->next_value;
} else {
info->id = 0;
info->ui32FWAddr = 0;
info->ui32CurrOp = 0;
info->ui32NextOp = 0;
}
return sizeof(struct pvr_sync_pt_info);
}
/* foreign sync handling */
static void pvr_sync_foreign_sync_pt_signaled(struct sync_fence *fence,
struct sync_fence_waiter *_waiter)
{
struct pvr_sync_fence_waiter *waiter =
(struct pvr_sync_fence_waiter *)_waiter;
unsigned long flags;
/* Complete the SW operation and free the sync if we can. If we can't,
* it will be checked by a later workqueue kick.
*/
complete_sync(waiter->kernel->fence_sync);
/* We can 'put' the fence now, but this function might be called in
* irq context so we must defer to WQ.
* This WQ is triggered in pvr_sync_defer_free, so adding it to the
* put list before that should guarantee it's cleaned up on the next
* wq run.
*/
spin_lock_irqsave(&sync_fence_put_list_spinlock, flags);
list_add_tail(&waiter->sync_fence->list, &sync_fence_put_list);
spin_unlock_irqrestore(&sync_fence_put_list_spinlock, flags);
pvr_sync_defer_free(waiter->kernel);
/* The completed sw-sync may allow other tasks to complete,
* so we need to allow them to progress.
*/
queue_work(pvr_sync_data.check_status_wq,
&pvr_sync_data.check_status_work);
kfree(waiter);
}
static struct pvr_sync_kernel_pair *
pvr_sync_create_waiter_for_foreign_sync(int fd)
{
struct pvr_sync_native_sync_prim *cleanup_sync = NULL;
struct pvr_sync_kernel_pair *kernel = NULL;
struct pvr_sync_fence_waiter *waiter;
struct pvr_sync_fence *sync_fence;
struct sync_fence *fence;
enum PVRSRV_ERROR error;
int err;
fence = sync_fence_fdget(fd);
if (!fence) {
pr_err("pvr_sync: %s: Failed to take reference on fence\n",
__func__);
goto err_out;
}
kernel = kmalloc(sizeof(struct pvr_sync_kernel_pair), GFP_KERNEL);
if (!kernel) {
pr_err("pvr_sync: %s: Failed to allocate sync kernel\n",
__func__);
goto err_put_fence;
}
INIT_LIST_HEAD(&kernel->cleanup_sync_list);
sync_fence = kmalloc(sizeof(struct pvr_sync_fence), GFP_KERNEL);
if (!sync_fence) {
pr_err("pvr_sync: %s: Failed to allocate pvr sync fence\n",
__func__);
goto err_free_kernel;
}
sync_fence->fence = fence;
error = sync_pool_get(&kernel->fence_sync,
fence->name, SYNC_PT_FOREIGN_FENCE_TYPE);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to allocate sync prim (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_free_sync_fence;
}
kernel->fence_sync->next_value++;
error = sync_pool_get(&cleanup_sync,
fence->name, SYNC_PT_FOREIGN_CLEANUP_TYPE);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to allocate cleanup sync prim (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_free_sync;
}
cleanup_sync->next_value++;
list_add(&cleanup_sync->cleanup_list, &kernel->cleanup_sync_list);
/* The custom waiter structure is freed in the waiter callback */
waiter = kmalloc(sizeof(struct pvr_sync_fence_waiter), GFP_KERNEL);
if (!waiter) {
pr_err("pvr_sync: %s: Failed to allocate waiter\n", __func__);
goto err_free_cleanup_sync;
}
waiter->kernel = kernel;
waiter->sync_fence = sync_fence;
sync_fence_waiter_init(&waiter->waiter,
pvr_sync_foreign_sync_pt_signaled);
err = sync_fence_wait_async(fence, &waiter->waiter);
if (err) {
if (err < 0) {
pr_err("pvr_sync: %s: Fence was in error state (%d)\n",
__func__, err);
/* Fall-thru */
}
/* -1 means the fence was broken, 1 means the fence already
* signalled. In either case, roll back what we've done and
* skip using this sync_pt for synchronization.
*/
goto err_free_waiter;
}
kernel->current_cleanup_sync = cleanup_sync;
err_out:
return kernel;
err_free_waiter:
kfree(waiter);
err_free_cleanup_sync:
list_del(&cleanup_sync->cleanup_list);
sync_pool_put(cleanup_sync);
err_free_sync:
sync_pool_put(kernel->fence_sync);
err_free_sync_fence:
kfree(sync_fence);
err_free_kernel:
kfree(kernel);
kernel = NULL;
err_put_fence:
sync_fence_put(fence);
goto err_out;
}
static
struct pvr_sync_pt *pvr_sync_create_pt(struct pvr_sync_timeline *timeline)
{
struct pvr_sync_data *sync_data;
struct pvr_sync_pt *pvr_pt = NULL;
sync_data = pvr_sync_create_sync_data(timeline->obj);
if (!sync_data) {
pr_err("pvr_sync: %s: Failed to create sync data\n", __func__);
goto err_out;
}
sync_data->kernel->fence_sync->next_value++;
pvr_pt = (struct pvr_sync_pt *)
sync_pt_create(timeline->obj, sizeof(struct pvr_sync_pt));
if (!pvr_pt) {
pr_err("pvr_sync: %s: Failed to create sync pt\n", __func__);
goto err_rollback_fence;
}
pvr_pt->sync_data = sync_data;
/* Increment the timeline next value */
pvr_pt->sync_data->timeline_update_value =
timeline->kernel->fence_sync->next_value++;
return pvr_pt;
err_rollback_fence:
sync_data->kernel->fence_sync->next_value--;
kref_put(&sync_data->kref, pvr_sync_free_sync_data);
err_out:
return NULL;
}
/* Predeclare the pvr_sync_fops as it's used for comparison to ensure the
* update_timeline_fd passed in to pvr_sync_append_fences() is a pvr_sync
* timeline.
*/
static const struct file_operations pvr_sync_fops;
enum PVRSRV_ERROR pvr_sync_append_fences(
const char *name,
const s32 check_fence_fd,
const s32 update_timeline_fd,
const u32 nr_updates,
const PRGXFWIF_UFO_ADDR *update_ufo_addresses,
const u32 *update_values,
const u32 nr_checks,
const PRGXFWIF_UFO_ADDR *check_ufo_addresses,
const u32 *check_values,
struct pvr_sync_append_data **append_sync_data)
{
struct pvr_sync_native_sync_prim **cleanup_sync_pos;
struct pvr_sync_pt *update_point = NULL;
struct sync_fence *update_fence = NULL;
struct pvr_sync_append_data *sync_data;
PRGXFWIF_UFO_ADDR *update_address_pos;
PRGXFWIF_UFO_ADDR *check_address_pos;
struct pvr_sync_timeline *timeline;
unsigned int num_used_sync_updates;
unsigned int num_used_sync_checks;
enum PVRSRV_ERROR err = PVRSRV_OK;
u32 *update_value_pos;
u32 *check_value_pos;
if ((nr_updates && (!update_ufo_addresses || !update_values)) ||
(nr_checks && (!check_ufo_addresses || !check_values))) {
err = PVRSRV_ERROR_INVALID_PARAMS;
goto err_out;
}
sync_data =
kzalloc(sizeof(struct pvr_sync_append_data), GFP_KERNEL);
if (!sync_data) {
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_out;
}
sync_data->update_fence_fd = -1;
if (update_timeline_fd >= 0) {
struct file *timeline_file;
/* We reserve the update fence FD before taking any operations
* as we do not want to fail (e.g. run out of FDs) after the
* kick operation has been submitted to the hw.
*/
sync_data->update_fence_fd = get_unused_fd();
if (sync_data->update_fence_fd < 0) {
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_free_append_data;
}
timeline_file = fget(update_timeline_fd);
if (!timeline_file) {
pr_err("pvr_sync: %s: Failed to open supplied timeline fd (%d)\n",
__func__, update_timeline_fd);
err = PVRSRV_ERROR_HANDLE_NOT_FOUND;
goto err_free_append_data;
}
if (timeline_file->f_op != &pvr_sync_fops) {
pr_err("pvr_sync: %s: Supplied timeline not pvr_sync timeline\n",
__func__);
fput(timeline_file);
err = PVRSRV_ERROR_INVALID_PARAMS;
goto err_free_append_data;
}
timeline = get_timeline(timeline_file->private_data);
/* We know this will not free the timeline as the user still
* has the fd referencing it.
*/
fput(timeline_file);
if (!timeline) {
pr_err("pvr_sync: %s: Supplied timeline has no private data\n",
__func__);
err = PVRSRV_ERROR_HANDLE_NOT_FOUND;
goto err_free_append_data;
}
update_point = pvr_sync_create_pt(timeline);
if (!update_point) {
pr_err("pvr_sync: %s: Failed to create sync point\n",
__func__);
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_free_append_data;
}
update_fence = sync_fence_create(name, &update_point->pt);
if (!update_fence) {
struct pvr_sync_native_sync_prim *fence_prim =
update_point->sync_data->kernel->fence_sync;
struct pvr_sync_native_sync_prim *timeline_prim =
timeline->kernel->fence_sync;
pr_err("pvr_sync: %s: Failed to create sync fence\n",
__func__);
err = PVRSRV_ERROR_OUT_OF_MEMORY;
/* If the point was created but the fence failed to be
* created, the point must be manually free'd as a
* fence has not yet taken ownership.
*/
/* First rollback the point's taken operations */
timeline_prim->next_value--;
fence_prim->next_value--;
pvr_sync_free_sync(&update_point->pt);
goto err_free_append_data;
}
sync_data->update_fence = update_fence;
sync_data->update_sync =
update_point->sync_data->kernel->fence_sync;
sync_data->update_timeline_sync =
timeline->kernel->fence_sync;
}
sync_data->nr_checks = nr_checks;
sync_data->nr_updates = nr_updates;
if (check_fence_fd >= 0) {
struct sync_fence *fence = sync_fence_fdget(check_fence_fd);
struct pvr_sync_kernel_pair *sync_kernel;
unsigned int points_on_fence = 0;
bool has_foreign_point = false;
struct sync_pt *sync_pt;
int j;
if (!fence) {
pr_err("pvr_sync: %s: Failed to read sync private data for fd %d\n",
__func__, check_fence_fd);
err = PVRSRV_ERROR_HANDLE_NOT_FOUND;
goto err_free_fence;
}
sync_data->check_fence = fence;
(void)j;
for_each_sync_pt(sync_pt, fence, j) {
struct pvr_sync_native_sync_prim *cleanup_sync = NULL;
struct pvr_sync_pt *pvr_pt;
if (!is_pvr_timeline_pt(sync_pt)) {
if (!sync_pt_get_status(sync_pt))
has_foreign_point = true;
continue;
}
pvr_pt = (struct pvr_sync_pt *)sync_pt;
sync_kernel = pvr_pt->sync_data->kernel;
if (!sync_kernel)
continue;
if (is_sync_met(sync_kernel->fence_sync))
continue;
/* We will use the above sync for "check" only. In this
* case also insert a "cleanup" update command into the
* opengl stream. This can later be used for checking
* if the sync prim could be freed.
*/
err = sync_pool_get(&cleanup_sync,
sync_pt_parent(&pvr_pt->pt)->name,
SYNC_PT_CLEANUP_TYPE);
if (err != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to allocate cleanup sync prim (%s)\n",
__func__,
PVRSRVGetErrorStringKM(err));
goto err_free_append_data;
}
list_add(&cleanup_sync->cleanup_list,
&sync_kernel->cleanup_sync_list);
sync_kernel->current_cleanup_sync = cleanup_sync;
points_on_fence++;
}
if (has_foreign_point)
points_on_fence++;
/* Each point has 1 check value, and 1 update value (for the
* cleanup fence).
*/
sync_data->nr_checks += points_on_fence;
sync_data->nr_updates += points_on_fence;
sync_data->nr_cleanup_syncs += points_on_fence;
}
if (update_point) {
/* A fence update requires 2 update values (fence and timeline)
*/
sync_data->nr_updates += 2;
}
if (sync_data->nr_updates > 0) {
sync_data->update_ufo_addresses =
kzalloc(sizeof(PRGXFWIF_UFO_ADDR) *
sync_data->nr_updates,
GFP_KERNEL);
if (!sync_data->update_ufo_addresses) {
pr_err("pvr_sync: %s: Failed to allocate update UFO address list\n",
__func__);
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_free_fence;
}
sync_data->update_values =
kzalloc(sizeof(u32) * sync_data->nr_updates,
GFP_KERNEL);
if (!sync_data->update_values) {
pr_err("pvr_sync: %s: Failed to allocate update value list\n",
__func__);
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_free_fence;
}
}
if (sync_data->nr_checks > 0) {
sync_data->check_ufo_addresses =
kzalloc(sizeof(PRGXFWIF_UFO_ADDR) *
sync_data->nr_checks,
GFP_KERNEL);
if (!sync_data->check_ufo_addresses) {
pr_err("pvr_sync: %s: Failed to allocate check UFO address list\n",
__func__);
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_free_fence;
}
sync_data->check_values =
kzalloc(sizeof(u32) * sync_data->nr_checks,
GFP_KERNEL);
if (!sync_data->check_values) {
pr_err("pvr_sync: %s: Failed to allocate check value list\n",
__func__);
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_free_fence;
}
}
if (sync_data->nr_cleanup_syncs > 0) {
sync_data->cleanup_syncs =
kzalloc(sizeof(struct pvr_sync_native_sync_prim *) *
sync_data->nr_cleanup_syncs, GFP_KERNEL);
if (!sync_data->cleanup_syncs) {
pr_err("pvr_sync: %s: Failed to allocate cleanup rollback list\n",
__func__);
err = PVRSRV_ERROR_OUT_OF_MEMORY;
goto err_free_fence;
}
}
update_address_pos = sync_data->update_ufo_addresses;
update_value_pos = sync_data->update_values;
check_address_pos = sync_data->check_ufo_addresses;
check_value_pos = sync_data->check_values;
cleanup_sync_pos = sync_data->cleanup_syncs;
/* Everything should be allocated/sanity checked. No errors are
* possible after this point.
*/
/* Append any check syncs */
if (sync_data->check_fence) {
struct sync_fence *fence = sync_data->check_fence;
bool has_foreign_point = false;
struct sync_pt *sync_pt;
int j;
(void)j;
for_each_sync_pt(sync_pt, fence, j) {
struct pvr_sync_pt *pvr_pt;
struct pvr_sync_kernel_pair *sync_kernel;
if (!is_pvr_timeline_pt(sync_pt)) {
if (!sync_pt_get_status(sync_pt))
has_foreign_point = true;
continue;
}
pvr_pt = (struct pvr_sync_pt *)sync_pt;
sync_kernel = pvr_pt->sync_data->kernel;
if (!sync_kernel ||
is_sync_met(sync_kernel->fence_sync)) {
continue;
}
(*check_address_pos++).ui32Addr =
sync_kernel->fence_sync->vaddr;
*check_value_pos++ =
sync_kernel->fence_sync->next_value;
(*update_address_pos++).ui32Addr =
sync_kernel->current_cleanup_sync->vaddr;
*update_value_pos++ =
++sync_kernel->current_cleanup_sync->next_value;
*cleanup_sync_pos++ = sync_kernel->current_cleanup_sync;
sync_kernel->current_cleanup_sync = NULL;
}
if (has_foreign_point) {
struct pvr_sync_kernel_pair *foreign_sync_kernel =
pvr_sync_create_waiter_for_foreign_sync(
check_fence_fd);
if (foreign_sync_kernel) {
struct pvr_sync_native_sync_prim *fence_sync =
foreign_sync_kernel->fence_sync;
struct pvr_sync_native_sync_prim *cleanup_sync =
foreign_sync_kernel->current_cleanup_sync;
(*check_address_pos++).ui32Addr =
fence_sync->vaddr;
*check_value_pos++ =
fence_sync->next_value;
(*update_address_pos++).ui32Addr =
cleanup_sync->vaddr;
*update_value_pos++ =
++cleanup_sync->next_value;
*cleanup_sync_pos++ = cleanup_sync;
foreign_sync_kernel->current_cleanup_sync = NULL;
}
}
}
/* Append the update sync (if requested) */
if (update_point) {
struct pvr_sync_data *sync_data =
update_point->sync_data;
struct pvr_sync_kernel_pair *sync_kernel =
sync_data->kernel;
(*update_address_pos++).ui32Addr =
sync_kernel->fence_sync->vaddr;
*update_value_pos++ =
sync_kernel->fence_sync->next_value;
(*update_address_pos++).ui32Addr =
timeline->kernel->fence_sync->vaddr;
/* Copy in the timeline next value (which was incremented
* when this point was created).
*/
sync_data->timeline_update_value =
timeline->kernel->fence_sync->next_value;
/* ...and set that to be updated when this kick is completed */
*update_value_pos++ =
sync_data->timeline_update_value;
}
/* We count the total number of sync points we attach, as it's possible
* some have become complete since the first loop through, or a waiter
* for a foreign point skipped (But they can never become un-complete,
* so it will only ever be the same or less, so the allocated arrays
* should still be sufficiently sized).
*/
num_used_sync_updates =
update_address_pos - sync_data->update_ufo_addresses;
num_used_sync_checks =
check_address_pos - sync_data->check_ufo_addresses;
sync_data->nr_checks = nr_checks + num_used_sync_checks;
sync_data->nr_updates = nr_updates + num_used_sync_updates;
/* Append original check and update sync values/addresses */
if (update_ufo_addresses)
memcpy(update_address_pos, update_ufo_addresses,
sizeof(PRGXFWIF_UFO_ADDR) * nr_updates);
if (update_values)
memcpy(update_value_pos, update_values,
sizeof(u32) * nr_updates);
if (check_ufo_addresses)
memcpy(check_address_pos, check_ufo_addresses,
sizeof(PRGXFWIF_UFO_ADDR) * nr_checks);
if (check_values)
memcpy(check_value_pos, check_values,
sizeof(u32) * nr_checks);
*append_sync_data = sync_data;
return PVRSRV_OK;
err_free_fence:
if (update_point) {
/* First rollback the taken operations */
timeline->kernel->fence_sync->next_value--;
update_point->sync_data->kernel->fence_sync->next_value--;
}
err_free_append_data:
pvr_sync_free_append_fences_data(sync_data);
err_out:
return err;
}
void pvr_sync_get_updates(const struct pvr_sync_append_data *sync_data,
u32 *nr_fences, PRGXFWIF_UFO_ADDR **ufo_addrs, u32 **values)
{
*nr_fences = sync_data->nr_updates;
*ufo_addrs = sync_data->update_ufo_addresses;
*values = sync_data->update_values;
}
void pvr_sync_get_checks(const struct pvr_sync_append_data *sync_data,
u32 *nr_fences, PRGXFWIF_UFO_ADDR **ufo_addrs, u32 **values)
{
*nr_fences = sync_data->nr_checks;
*ufo_addrs = sync_data->check_ufo_addresses;
*values = sync_data->check_values;
}
void pvr_sync_rollback_append_fences(struct pvr_sync_append_data *sync_data)
{
u32 i;
if (!sync_data)
return;
for (i = 0; i < sync_data->nr_cleanup_syncs; i++) {
struct pvr_sync_native_sync_prim *cleanup_sync =
sync_data->cleanup_syncs[i];
/* If this cleanup was called on a partially-created data set
* it's possible to have NULL cleanup sync pointers.
*/
if (!cleanup_sync)
continue;
cleanup_sync->next_value--;
}
/* If there was an update, rollback the next values taken on the
* fence and timeline. This must be done before the sync_fence_put()
* as that may free the corresponding fence.
*/
if (sync_data->update_sync) {
BUG_ON(sync_data->update_sync->next_value != 1);
sync_data->update_sync->next_value = 0;
sync_data->update_sync = NULL;
}
if (sync_data->update_timeline_sync) {
BUG_ON(sync_data->update_timeline_sync->next_value == 0);
sync_data->update_timeline_sync->next_value--;
sync_data->update_timeline_sync = NULL;
}
}
int pvr_sync_get_update_fd(struct pvr_sync_append_data *sync_data)
{
int fd = -EINVAL;
if (!sync_data || !sync_data->update_fence ||
sync_data->update_fence_fd < 0)
goto err_out;
fd = sync_data->update_fence_fd;
sync_data->update_fence_fd = -1;
sync_fence_install(sync_data->update_fence, fd);
/* Note: It is invalid for an FD to have been installed on the update
* fence then fput called - as this would leave a dangling reference
* in the FD table. Set it to NULL so the free_append_fences_data()
* call doesn't fput it.
*/
sync_data->update_fence = NULL;
err_out:
return fd;
}
void pvr_sync_free_append_fences_data(struct pvr_sync_append_data *sync_data)
{
if (!sync_data)
return;
if (sync_data->check_fence)
sync_fence_put(sync_data->check_fence);
if (sync_data->update_fence)
sync_fence_put(sync_data->update_fence);
if (sync_data->update_fence_fd >= 0)
put_unused_fd(sync_data->update_fence_fd);
kfree(sync_data->update_ufo_addresses);
kfree(sync_data->update_values);
kfree(sync_data->check_ufo_addresses);
kfree(sync_data->check_values);
kfree(sync_data->cleanup_syncs);
kfree(sync_data);
}
void pvr_sync_nohw_complete_fences(struct pvr_sync_append_data *sync_data)
{
u32 i;
if (!sync_data)
return;
for (i = 0; i < sync_data->nr_cleanup_syncs; i++) {
struct pvr_sync_native_sync_prim *cleanup_sync =
sync_data->cleanup_syncs[i];
if (!cleanup_sync)
continue;
complete_sync(cleanup_sync);
}
if (sync_data->update_sync)
complete_sync(sync_data->update_sync);
if (sync_data->update_timeline_sync)
complete_sync(sync_data->update_timeline_sync);
}
/* ioctl and fops handling */
static int pvr_sync_open(struct inode *inode, struct file *file)
{
struct pvr_sync_timeline_wrapper *timeline_wrapper;
struct pvr_sync_timeline *timeline;
char task_comm[TASK_COMM_LEN];
enum PVRSRV_ERROR error;
int err = -ENOMEM;
get_task_comm(task_comm, current);
timeline_wrapper = (struct pvr_sync_timeline_wrapper *)
sync_timeline_create(&pvr_sync_timeline_ops,
sizeof(struct pvr_sync_timeline_wrapper), task_comm);
if (!timeline_wrapper) {
pr_err("pvr_sync: %s: sync_timeline_create failed\n", __func__);
goto err_out;
}
timeline = kmalloc(sizeof(struct pvr_sync_timeline), GFP_KERNEL);
if (!timeline) {
pr_err("pvr_sync: %s: Out of memory\n", __func__);
goto err_free_timeline_wrapper;
}
timeline->kernel = kzalloc(sizeof(struct pvr_sync_kernel_pair),
GFP_KERNEL);
if (!timeline->kernel) {
pr_err("pvr_sync: %s: Out of memory\n", __func__);
goto err_free_timeline;
}
INIT_LIST_HEAD(&timeline->kernel->cleanup_sync_list);
OSAcquireBridgeLock();
PMRLock();
error = sync_pool_get(&timeline->kernel->fence_sync,
task_comm, SYNC_TL_TYPE);
PMRUnlock();
OSReleaseBridgeLock();
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to allocate sync prim (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_free_timeline_kernel;
}
timeline_wrapper->timeline = timeline;
timeline->obj = &timeline_wrapper->obj;
kref_init(&timeline->kref);
mutex_lock(&timeline_list_mutex);
list_add_tail(&timeline->list, &timeline_list);
mutex_unlock(&timeline_list_mutex);
DPF("%s: # %s", __func__, debug_info_timeline(timeline));
file->private_data = timeline_wrapper;
err = 0;
err_out:
return err;
err_free_timeline_kernel:
kfree(timeline->kernel);
err_free_timeline:
kfree(timeline);
/* Use a NULL timeline to detect this partially-setup timeline in the
* timeline release function (called by sync_timeline_destroy) and
* handle it appropriately.
*/
timeline_wrapper->timeline = NULL;
err_free_timeline_wrapper:
sync_timeline_destroy(&timeline_wrapper->obj);
goto err_out;
}
static int pvr_sync_close(struct inode *inode, struct file *file)
{
struct sync_timeline *obj = file->private_data;
if (is_pvr_timeline(obj)) {
DPF("%s: # %s", __func__,
debug_info_timeline(get_timeline(obj)));
}
sync_timeline_destroy(obj);
return 0;
}
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->obj->name, data.szName, sizeof(timeline->obj->name));
mutex_lock(&sync_pool_mutex);
strlcpy(timeline->kernel->fence_sync->class, data.szName,
sizeof(timeline->kernel->fence_sync->class));
mutex_unlock(&sync_pool_mutex);
err:
return err;
}
#ifndef CONFIG_SW_SYNC_USER
static long pvr_sync_ioctl_force_sw_only(struct pvr_sync_timeline *timeline,
void **private_data)
{
struct sw_sync_timeline *sw_sync_timeline;
/* We can only convert an empty GPU timeline */
if (timeline->kernel->fence_sync->next_value)
return -EFAULT;
/* Create a sw_sync timeline with the old GPU timeline's name */
sw_sync_timeline = sw_sync_timeline_create(timeline->obj->name);
if (!sw_sync_timeline)
return -ENOMEM;
/* Destroy the old GPU timeline and update the struct file */
DPF("%s: # %s", __func__, debug_info_timeline(timeline));
sync_timeline_destroy(timeline->obj);
*private_data = sw_sync_timeline;
return 0;
}
static long pvr_sync_ioctl_sw_create_fence(struct sw_sync_timeline *timeline,
void __user *user_data)
{
struct sw_sync_create_fence_data data;
struct sync_fence *fence;
int fd = get_unused_fd();
struct sync_pt *sync_pt;
int err = -EFAULT;
if (fd < 0) {
pr_err("pvr_sync: %s: Failed to find unused fd (%d)\n",
__func__, fd);
goto err_out;
}
if (copy_from_user(&data, user_data, sizeof(data)))
goto err_put_fd;
sync_pt = sw_sync_pt_create(timeline, data.value);
if (!sync_pt) {
pr_err("pvr_sync: %s: Failed to create a sync point (%d)\n",
__func__, fd);
err = -ENOMEM;
goto err_put_fd;
}
data.name[sizeof(data.name) - 1] = '\0';
fence = sync_fence_create(data.name, sync_pt);
if (!fence) {
pr_err("pvr_sync: %s: Failed to create a fence (%d)\n",
__func__, fd);
sync_pt_free(sync_pt);
err = -ENOMEM;
goto err_put_fd;
}
data.fence = fd;
if (copy_to_user(user_data, &data, sizeof(data)))
goto err_put_fence;
sync_fence_install(fence, fd);
err = 0;
err_out:
return err;
err_put_fence:
sync_fence_put(fence);
err_put_fd:
put_unused_fd(fd);
goto err_out;
}
static long pvr_sync_ioctl_sw_inc(struct sw_sync_timeline *timeline,
void __user *user_data)
{
u32 value;
if (copy_from_user(&value, user_data, sizeof(value)))
return -EFAULT;
sw_sync_timeline_inc(timeline, value);
return 0;
}
#endif /* !CONFIG_SW_SYNC_USER */
static long
pvr_sync_ioctl(struct file *file, unsigned int cmd, unsigned long __user arg)
{
struct sync_timeline *obj = file->private_data;
void __user *user_data = (void __user *)arg;
long err = -ENOTTY;
if (is_pvr_timeline(obj)) {
struct pvr_sync_timeline *pvr = get_timeline(obj);
switch (cmd) {
case PVR_SYNC_IOC_RENAME:
err = pvr_sync_ioctl_rename(pvr, user_data);
break;
#ifndef CONFIG_SW_SYNC_USER
case PVR_SYNC_IOC_FORCE_SW_ONLY:
err = pvr_sync_ioctl_force_sw_only(pvr,
&file->private_data);
break;
#endif /* !CONFIG_SW_SYNC_USER */
default:
break;
}
} else {
#ifndef CONFIG_SW_SYNC_USER
struct sw_sync_timeline *sw = file->private_data;
switch (cmd) {
case SW_SYNC_IOC_CREATE_FENCE:
err = pvr_sync_ioctl_sw_create_fence(sw, user_data);
break;
case SW_SYNC_IOC_INC:
err = pvr_sync_ioctl_sw_inc(sw, user_data);
break;
default:
break;
}
#endif /* !CONFIG_SW_SYNC_USER */
}
return err;
}
static void
pvr_sync_check_status_work_queue_function(struct work_struct *data)
{
/* A completed SW operation may un-block the GPU */
PVRSRVCheckStatus(NULL);
}
/* Returns true if the freelist still has entries, else false if empty */
static bool
pvr_sync_clean_freelist(void)
{
struct pvr_sync_kernel_pair *kernel, *k;
struct pvr_sync_fence *sync_fence, *f;
LIST_HEAD(unlocked_free_list);
unsigned long flags;
bool freelist_empty;
/* We can't call PVRSRVServerSyncFreeKM directly in this loop because
* that will take the mmap mutex. We can't take mutexes while we have
* this list locked with a spinlock. So move all the items we want to
* free to another, local list (no locking required) and process it
* in a second loop.
*/
spin_lock_irqsave(&sync_prim_free_list_spinlock, flags);
list_for_each_entry_safe(kernel, k, &sync_prim_free_list, list) {
bool in_use = false;
struct list_head *pos;
/* Check if this sync is not used anymore. */
if (!is_sync_met(kernel->fence_sync))
continue;
list_for_each(pos, &kernel->cleanup_sync_list) {
struct pvr_sync_native_sync_prim *cleanup_sync =
list_entry(pos,
struct pvr_sync_native_sync_prim,
cleanup_list);
if (!is_sync_met(cleanup_sync)) {
in_use = true;
break;
}
}
if (in_use)
continue;
/* Remove the entry from the free list. */
list_move_tail(&kernel->list, &unlocked_free_list);
}
/* Wait and loop if there are still syncs on the free list (IE
* are still in use by the HW).
*/
freelist_empty = list_empty(&sync_prim_free_list);
spin_unlock_irqrestore(&sync_prim_free_list_spinlock, flags);
OSAcquireBridgeLock();
list_for_each_entry_safe(kernel, k, &unlocked_free_list, list) {
struct list_head *pos, *n;
list_del(&kernel->list);
sync_pool_put(kernel->fence_sync);
list_for_each_safe(pos, n, &kernel->cleanup_sync_list) {
struct pvr_sync_native_sync_prim *cleanup_sync =
list_entry(pos,
struct pvr_sync_native_sync_prim,
cleanup_list);
list_del(&cleanup_sync->cleanup_list);
sync_pool_put(cleanup_sync);
}
kfree(kernel);
}
OSReleaseBridgeLock();
/* sync_fence_put() must be called from process/WQ context
* because it uses fput(), which is not allowed to be called
* from interrupt context in kernels <3.6.
*/
INIT_LIST_HEAD(&unlocked_free_list);
spin_lock_irqsave(&sync_fence_put_list_spinlock, flags);
list_for_each_entry_safe(sync_fence, f, &sync_fence_put_list, list) {
list_move_tail(&sync_fence->list, &unlocked_free_list);
}
spin_unlock_irqrestore(&sync_fence_put_list_spinlock, flags);
list_for_each_entry_safe(sync_fence, f, &unlocked_free_list, list) {
list_del(&sync_fence->list);
sync_fence_put(sync_fence->fence);
kfree(sync_fence);
}
return !freelist_empty;
}
static void
pvr_sync_defer_free_work_queue_function(struct work_struct *data)
{
enum PVRSRV_ERROR error = PVRSRV_OK;
void *event_object;
error = OSEventObjectOpen(pvr_sync_data.event_object_handle,
&event_object);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Error opening event object (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
return;
}
while (pvr_sync_clean_freelist()) {
error = OSEventObjectWait(event_object);
switch (error) {
case PVRSRV_OK:
case PVRSRV_ERROR_TIMEOUT:
/* Timeout is normal behaviour */
continue;
default:
pr_err("pvr_sync: %s: Error waiting for event object (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
break;
}
}
error = OSEventObjectClose(event_object);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Error closing event object (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
}
}
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_update_all_timelines(void *command_complete_handle)
{
struct pvr_sync_timeline *timeline, *n;
mutex_lock(&timeline_list_mutex);
list_for_each_entry(timeline, &timeline_list, list) {
/* If a timeline is destroyed via pvr_sync_release_timeline()
* in parallel with a call to pvr_sync_update_all_timelines(),
* the timeline_list_mutex will block destruction of the
* 'timeline' pointer. Use kref_get_unless_zero() to detect
* and handle this race. Skip the timeline if it's being
* destroyed, blocked only on the timeline_list_mutex.
*/
timeline->valid =
kref_get_unless_zero(&timeline->kref) ? true : false;
}
list_for_each_entry_safe(timeline, n, &timeline_list, list) {
/* We know timeline is valid at this point because we're
* holding the list lock (so pvr_sync_destroy_timeline() has
* to wait).
*/
void *obj = timeline->obj;
/* If we're racing with pvr_sync_release_timeline(), ignore */
if (!timeline->valid)
continue;
/* If syncs have signaled on the GPU, echo this in pvr_sync.
*
* At this point we know the timeline is valid, but obj might
* have raced and been set to NULL. It's only important that
* we use NULL / non-NULL consistently with the if() and call
* to sync_timeline_signal() -- the timeline->obj can't be
* freed (pvr_sync_release_timeline() will be stuck waiting
* for the timeline_list_mutex) but it might have been made
* invalid by the base sync driver, in which case this call
* will bounce harmlessly.
*/
if (obj)
sync_timeline_signal(obj);
/* We're already holding the timeline_list_mutex */
kref_put(&timeline->kref, pvr_sync_destroy_timeline_locked);
}
mutex_unlock(&timeline_list_mutex);
}
enum PVRSRV_ERROR pvr_sync_init(void)
{
enum PVRSRV_ERROR error;
int err;
DPF("%s", __func__);
atomic_set(&pvr_sync_data.sync_id, 0);
error = PVRSRVAcquireDeviceDataKM(0, PVRSRV_DEVICE_TYPE_RGX,
&pvr_sync_data.device_cookie);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to initialise services (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_out;
}
error = AcquireGlobalEventObjectServer(
&pvr_sync_data.event_object_handle);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to acquire global event object (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_release_device_data;
}
OSAcquireBridgeLock();
error = SyncPrimContextCreate(pvr_sync_data.device_cookie,
&pvr_sync_data.sync_prim_context);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to create sync prim context (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
OSReleaseBridgeLock();
goto err_release_event_object;
}
OSReleaseBridgeLock();
pvr_sync_data.defer_free_wq =
create_freezable_workqueue("pvr_sync_defer_free_workqueue");
if (!pvr_sync_data.defer_free_wq) {
pr_err("pvr_sync: %s: Failed to create pvr_sync defer_free workqueue\n",
__func__);
goto err_free_sync_context;
}
INIT_WORK(&pvr_sync_data.defer_free_work,
pvr_sync_defer_free_work_queue_function);
pvr_sync_data.check_status_wq =
create_freezable_workqueue("pvr_sync_check_status_workqueue");
if (!pvr_sync_data.check_status_wq) {
pr_err("pvr_sync: %s: Failed to create pvr_sync check_status workqueue\n",
__func__);
goto err_destroy_defer_free_wq;
}
INIT_WORK(&pvr_sync_data.check_status_work,
pvr_sync_check_status_work_queue_function);
error = PVRSRVRegisterCmdCompleteNotify(
&pvr_sync_data.command_complete_handle,
&pvr_sync_update_all_timelines,
&pvr_sync_data.device_cookie);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to register MISR notification (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_destroy_status_wq;
}
error = PVRSRVRegisterDbgRequestNotify(
&pvr_sync_data.debug_notify_handle,
pvr_sync_debug_request,
DEBUG_REQUEST_ANDROIDSYNC,
NULL);
if (error != PVRSRV_OK) {
pr_err("pvr_sync: %s: Failed to register debug notifier (%s)\n",
__func__, PVRSRVGetErrorStringKM(error));
goto err_unregister_cmd_complete;
}
err = misc_register(&pvr_sync_device);
if (err) {
pr_err("pvr_sync: %s: Failed to register pvr_sync device (%d)\n",
__func__, err);
error = PVRSRV_ERROR_RESOURCE_UNAVAILABLE;
goto err_unregister_dbg;
}
error = PVRSRV_OK;
return error;
err_unregister_dbg:
PVRSRVUnregisterDbgRequestNotify(pvr_sync_data.debug_notify_handle);
err_unregister_cmd_complete:
PVRSRVUnregisterCmdCompleteNotify(
pvr_sync_data.command_complete_handle);
err_destroy_status_wq:
destroy_workqueue(pvr_sync_data.check_status_wq);
err_destroy_defer_free_wq:
destroy_workqueue(pvr_sync_data.defer_free_wq);
err_free_sync_context:
OSAcquireBridgeLock();
SyncPrimContextDestroy(pvr_sync_data.sync_prim_context);
OSReleaseBridgeLock();
err_release_event_object:
ReleaseGlobalEventObjectServer(pvr_sync_data.event_object_handle);
err_release_device_data:
PVRSRVReleaseDeviceDataKM(pvr_sync_data.device_cookie);
err_out:
return error;
}
void pvr_sync_deinit(void)
{
DPF("%s", __func__);
misc_deregister(&pvr_sync_device);
PVRSRVUnregisterDbgRequestNotify(pvr_sync_data.debug_notify_handle);
PVRSRVUnregisterCmdCompleteNotify(
pvr_sync_data.command_complete_handle);
/* This will drain the workqueue, so we guarantee that all deferred
* syncs are free'd before returning.
*/
destroy_workqueue(pvr_sync_data.defer_free_wq);
destroy_workqueue(pvr_sync_data.check_status_wq);
OSAcquireBridgeLock();
sync_pool_clear();
SyncPrimContextDestroy(pvr_sync_data.sync_prim_context);
OSReleaseBridgeLock();
ReleaseGlobalEventObjectServer(pvr_sync_data.event_object_handle);
PVRSRVReleaseDeviceDataKM(pvr_sync_data.device_cookie);
}