/* * drm_sync_helper.c: software fence and helper functions for fences and * reservations used for dma buffer access synchronization between drivers. * * Copyright 2014 Google, Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include static DEFINE_SPINLOCK(sw_fence_lock); void drm_add_reservation(struct reservation_object *resv, struct reservation_object **resvs, unsigned long *excl_resvs_bitmap, unsigned int *num_resvs, bool exclusive) { unsigned int r; for (r = 0; r < *num_resvs; r++) { if (resvs[r] == resv) return; } resvs[*num_resvs] = resv; if (exclusive) set_bit(*num_resvs, excl_resvs_bitmap); (*num_resvs)++; } EXPORT_SYMBOL(drm_add_reservation); int drm_lock_reservations(struct reservation_object **resvs, unsigned int num_resvs, struct ww_acquire_ctx *ctx) { unsigned int r; struct reservation_object *slow_res = NULL; ww_acquire_init(ctx, &reservation_ww_class); retry: for (r = 0; r < num_resvs; r++) { int ret; /* skip the resv we locked with slow lock */ if (resvs[r] == slow_res) { slow_res = NULL; continue; } ret = ww_mutex_lock(&resvs[r]->lock, ctx); if (ret < 0) { unsigned int slow_r = r; /* * undo all the locks we already done, * in reverse order */ while (r > 0) { r--; ww_mutex_unlock(&resvs[r]->lock); } if (slow_res) ww_mutex_unlock(&slow_res->lock); if (ret == -EDEADLK) { slow_res = resvs[slow_r]; ww_mutex_lock_slow(&slow_res->lock, ctx); goto retry; } ww_acquire_fini(ctx); return ret; } } ww_acquire_done(ctx); return 0; } EXPORT_SYMBOL(drm_lock_reservations); void drm_unlock_reservations(struct reservation_object **resvs, unsigned int num_resvs, struct ww_acquire_ctx *ctx) { unsigned int r; for (r = 0; r < num_resvs; r++) ww_mutex_unlock(&resvs[r]->lock); ww_acquire_fini(ctx); } EXPORT_SYMBOL(drm_unlock_reservations); static void reservation_cb_fence_cb(struct fence *fence, struct fence_cb *cb) { struct drm_reservation_fence_cb *rfcb = container_of(cb, struct drm_reservation_fence_cb, base); struct drm_reservation_cb *rcb = rfcb->parent; if (atomic_dec_and_test(&rcb->count)) schedule_work(&rcb->work); } static void reservation_cb_cleanup(struct drm_reservation_cb *rcb) { unsigned cb; for (cb = 0; cb < rcb->num_fence_cbs; cb++) { if (rcb->fence_cbs[cb]) { fence_remove_callback(rcb->fence_cbs[cb]->fence, &rcb->fence_cbs[cb]->base); fence_put(rcb->fence_cbs[cb]->fence); kfree(rcb->fence_cbs[cb]); rcb->fence_cbs[cb] = NULL; } } kfree(rcb->fence_cbs); rcb->fence_cbs = NULL; rcb->num_fence_cbs = 0; } static void reservation_cb_work(struct work_struct *pwork) { struct drm_reservation_cb *rcb = container_of(pwork, struct drm_reservation_cb, work); /* * clean up everything before calling the callback, because the callback * may free structure containing rcb and work_struct */ reservation_cb_cleanup(rcb); rcb->func(rcb, rcb->context); } static int reservation_cb_add_fence_cb(struct drm_reservation_cb *rcb, struct fence *fence) { int ret = 0; struct drm_reservation_fence_cb *fence_cb; struct drm_reservation_fence_cb **new_fence_cbs; new_fence_cbs = krealloc(rcb->fence_cbs, (rcb->num_fence_cbs + 1) * sizeof(struct drm_reservation_fence_cb *), GFP_KERNEL); if (!new_fence_cbs) return -ENOMEM; rcb->fence_cbs = new_fence_cbs; fence_cb = kzalloc(sizeof(struct drm_reservation_fence_cb), GFP_KERNEL); if (!fence_cb) return -ENOMEM; /* * do not want for fence to disappear on us while we are waiting for * callback and we need it in case we want to remove callbacks */ fence_get(fence); fence_cb->fence = fence; fence_cb->parent = rcb; rcb->fence_cbs[rcb->num_fence_cbs] = fence_cb; atomic_inc(&rcb->count); ret = fence_add_callback(fence, &fence_cb->base, reservation_cb_fence_cb); if (ret == -ENOENT) { /* already signaled */ atomic_dec(&rcb->count); fence_put(fence_cb->fence); kfree(fence_cb); ret = 0; } else if (ret < 0) { atomic_dec(&rcb->count); fence_put(fence_cb->fence); kfree(fence_cb); return ret; } else { rcb->num_fence_cbs++; } return ret; } void drm_reservation_cb_init(struct drm_reservation_cb *rcb, drm_reservation_cb_func_t func, void *context) { INIT_WORK(&rcb->work, reservation_cb_work); atomic_set(&rcb->count, 1); rcb->num_fence_cbs = 0; rcb->fence_cbs = NULL; rcb->func = func; rcb->context = context; } EXPORT_SYMBOL(drm_reservation_cb_init); int drm_reservation_cb_add(struct drm_reservation_cb *rcb, struct reservation_object *resv, bool exclusive) { int ret = 0; struct fence *fence; unsigned shared_count = 0, f; struct fence **shared_fences = NULL; /* enum all the fences in the reservation and add callbacks */ ret = reservation_object_get_fences_rcu(resv, &fence, &shared_count, &shared_fences); if (ret < 0) return ret; if (fence) { ret = reservation_cb_add_fence_cb(rcb, fence); if (ret < 0) { reservation_cb_cleanup(rcb); goto error; } } if (exclusive) { for (f = 0; f < shared_count; f++) { ret = reservation_cb_add_fence_cb(rcb, shared_fences[f]); if (ret < 0) { reservation_cb_cleanup(rcb); goto error; } } } error: if (fence) fence_put(fence); if (shared_fences) { for (f = 0; f < shared_count; f++) fence_put(shared_fences[f]); kfree(shared_fences); } return ret; } EXPORT_SYMBOL(drm_reservation_cb_add); void drm_reservation_cb_done(struct drm_reservation_cb *rcb) { /* * we need to decrement from initial 1 * and trigger the callback in case all the * fences were already triggered */ if (atomic_dec_and_test(&rcb->count)) { /* * we could call the callback here directly but in case * the callback function needs to lock the same mutex * as our caller it could cause a deadlock, so it is * safer to call it from a worker */ schedule_work(&rcb->work); } } EXPORT_SYMBOL(drm_reservation_cb_done); void drm_reservation_cb_fini(struct drm_reservation_cb *rcb) { /* make sure no work will be triggered */ atomic_set(&rcb->count, 0); cancel_work_sync(&rcb->work); reservation_cb_cleanup(rcb); } EXPORT_SYMBOL(drm_reservation_cb_fini); static bool sw_fence_enable_signaling(struct fence *f) { return true; } static const char *sw_fence_get_get_driver_name(struct fence *fence) { return "drm_sync_helper"; } static const char *sw_fence_get_timeline_name(struct fence *f) { return "drm_sync.sw"; } static const struct fence_ops sw_fence_ops = { .get_driver_name = sw_fence_get_get_driver_name, .get_timeline_name = sw_fence_get_timeline_name, .enable_signaling = sw_fence_enable_signaling, .signaled = NULL, .wait = fence_default_wait, .release = NULL }; struct fence *drm_sw_fence_new(unsigned int context, unsigned seqno) { struct fence *fence; fence = kzalloc(sizeof(*fence), GFP_KERNEL); if (!fence) return ERR_PTR(-ENOMEM); fence_init(fence, &sw_fence_ops, &sw_fence_lock, context, seqno); return fence; } EXPORT_SYMBOL(drm_sw_fence_new);