| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * GCIP support of DMA fences. |
| * |
| * Copyright (C) 2023 Google LLC |
| */ |
| |
| #include <linux/bitops.h> |
| #include <linux/device.h> |
| #include <linux/dma-fence.h> |
| #include <linux/dma-fence-unwrap.h> |
| #include <linux/err.h> |
| #include <linux/file.h> |
| #include <linux/ktime.h> |
| #include <linux/list.h> |
| #include <linux/seq_file.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/string.h> |
| #include <linux/sync_file.h> |
| #include <linux/time.h> |
| |
| #include <gcip/gcip-dma-fence.h> |
| |
| #define to_gfence(fence) container_of(fence, struct gcip_dma_fence, fence) |
| |
| int gcip_signal_dma_fence_with_status(struct dma_fence *fence, int error, bool ignore_signaled) |
| { |
| unsigned long flags; |
| int ret; |
| struct dma_fence *cur; |
| struct dma_fence_unwrap iter; |
| |
| if (error > 0) |
| error = -error; |
| if (unlikely(error < -MAX_ERRNO)) |
| return -EINVAL; |
| |
| /* If not ignoring signaled, only return busy if ALL fences are already signaled. */ |
| ret = ignore_signaled ? 0 : -EBUSY; |
| |
| /* |
| * If @fence is a dma_fence_array, iterate over each fence in the array and signal it. |
| * This loop will be executed exactly once for cur == @fence, if @fence is not an array. |
| */ |
| dma_fence_unwrap_for_each(cur, &iter, fence) { |
| spin_lock_irqsave(cur->lock, flags); |
| /* don't signal fence twice */ |
| if (unlikely(test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &cur->flags))) |
| goto cont_unlock; |
| if (error) |
| dma_fence_set_error(cur, error); |
| ret = dma_fence_signal_locked(cur); |
| cont_unlock: |
| spin_unlock_irqrestore(cur->lock, flags); |
| } |
| |
| return ret; |
| } |
| |
| static const char *sync_status_str(int status) |
| { |
| if (status < 0) |
| return "error"; |
| if (status > 0) |
| return "signaled"; |
| return "active"; |
| } |
| |
| struct gcip_dma_fence_manager *gcip_dma_fence_manager_create(struct device *dev) |
| { |
| struct gcip_dma_fence_manager *mgr = devm_kzalloc(dev, sizeof(*mgr), GFP_KERNEL); |
| |
| if (!mgr) |
| return ERR_PTR(-ENOMEM); |
| |
| INIT_LIST_HEAD(&mgr->fence_list_head); |
| spin_lock_init(&mgr->fence_list_lock); |
| mgr->dev = dev; |
| |
| return mgr; |
| } |
| |
| const char *gcip_dma_fence_get_timeline_name(struct dma_fence *fence) |
| { |
| struct gcip_dma_fence *gfence = to_gfence(fence); |
| |
| return gfence->timeline_name; |
| } |
| |
| bool gcip_dma_fence_always_true(struct dma_fence *fence) |
| { |
| return true; |
| } |
| |
| int gcip_dma_fence_init(struct gcip_dma_fence_manager *mgr, struct gcip_dma_fence *gfence, |
| struct gcip_dma_fence_data *data) |
| { |
| unsigned long flags; |
| int fd; |
| struct sync_file *sync_file; |
| int ret; |
| |
| strscpy(gfence->timeline_name, data->timeline_name, GCIP_FENCE_TIMELINE_NAME_LEN); |
| |
| spin_lock_init(&gfence->lock); |
| INIT_LIST_HEAD(&gfence->fence_list); |
| gfence->mgr = mgr; |
| |
| dma_fence_init(&gfence->fence, data->ops, &gfence->lock, dma_fence_context_alloc(1), |
| data->seqno); |
| GCIP_DMA_FENCE_LIST_LOCK(mgr, flags); |
| list_add_tail(&gfence->fence_list, &mgr->fence_list_head); |
| GCIP_DMA_FENCE_LIST_UNLOCK(mgr, flags); |
| |
| if (data->after_init) { |
| ret = data->after_init(gfence); |
| if (ret) { |
| dev_err(mgr->dev, "DMA fence init failed on after_init: %d", ret); |
| goto err_put_fence; |
| } |
| } |
| fd = get_unused_fd_flags(O_CLOEXEC); |
| if (fd < 0) { |
| ret = fd; |
| dev_err(mgr->dev, "Failed to get FD: %d", ret); |
| goto err_put_fence; |
| } |
| sync_file = sync_file_create(&gfence->fence); |
| if (!sync_file) { |
| dev_err(mgr->dev, "Failed to create sync file"); |
| ret = -ENOMEM; |
| goto err_put_fd; |
| } |
| /* sync_file holds the reference to fence, so we can drop our reference. */ |
| dma_fence_put(&gfence->fence); |
| |
| fd_install(fd, sync_file->file); |
| data->fence = fd; |
| return 0; |
| |
| err_put_fd: |
| put_unused_fd(fd); |
| err_put_fence: |
| dma_fence_put(&gfence->fence); |
| return ret; |
| } |
| |
| void gcip_dma_fence_exit(struct gcip_dma_fence *gfence) |
| { |
| unsigned long flags; |
| |
| GCIP_DMA_FENCE_LIST_LOCK(gfence->mgr, flags); |
| list_del(&gfence->fence_list); |
| GCIP_DMA_FENCE_LIST_UNLOCK(gfence->mgr, flags); |
| } |
| |
| int gcip_dma_fence_status(int fence, int *status) |
| { |
| struct dma_fence *fencep; |
| |
| fencep = sync_file_get_fence(fence); |
| if (!fencep) |
| return -EBADF; |
| *status = dma_fence_get_status(fencep); |
| dma_fence_put(fencep); |
| return 0; |
| } |
| |
| int gcip_dma_fence_signal(int fence, int error, bool ignore_signaled) |
| { |
| struct dma_fence *fencep; |
| int ret; |
| |
| fencep = sync_file_get_fence(fence); |
| if (!fencep) |
| return -EBADF; |
| ret = gcip_signal_dma_fence_with_status(fencep, error, ignore_signaled); |
| dma_fence_put(fencep); |
| return ret; |
| } |
| |
| int gcip_dma_fenceptr_signal(struct gcip_dma_fence *gfence, int error, bool ignore_signaled) |
| { |
| return gcip_signal_dma_fence_with_status(&gfence->fence, error, ignore_signaled); |
| } |
| |
| void gcip_dma_fence_show(struct gcip_dma_fence *gfence, struct seq_file *s) |
| { |
| struct dma_fence *fence = &gfence->fence; |
| |
| spin_lock_irq(&gfence->lock); |
| |
| seq_printf(s, "%s-%s %llu-%llu %s", fence->ops->get_driver_name(fence), |
| fence->ops->get_timeline_name(fence), fence->context, fence->seqno, |
| sync_status_str(dma_fence_get_status_locked(fence))); |
| |
| if (test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &fence->flags)) { |
| struct timespec64 ts = ktime_to_timespec64(fence->timestamp); |
| |
| seq_printf(s, " @%lld.%09ld", (s64)ts.tv_sec, ts.tv_nsec); |
| } |
| |
| if (fence->error) |
| seq_printf(s, " err=%d", fence->error); |
| |
| spin_unlock_irq(&gfence->lock); |
| } |
| |
| struct dma_fence *gcip_dma_fence_merge_fds(int num_fences, int *fence_fds) |
| { |
| struct dma_fence **fences; |
| struct dma_fence *tmp; |
| struct dma_fence *result; |
| int i = 0; |
| |
| if (!num_fences) |
| return ERR_PTR(-EINVAL); |
| |
| fences = kcalloc(num_fences, sizeof(*fences), GFP_KERNEL); |
| if (!fences) |
| return ERR_PTR(-ENOMEM); |
| |
| for (i = 0; i < num_fences; i++) { |
| fences[i] = sync_file_get_fence(fence_fds[i]); |
| if (!fences[i]) { |
| result = ERR_PTR(-ENOENT); |
| goto out; |
| } |
| } |
| |
| result = dma_fence_unwrap_merge(fences[0]); |
| if (!result) { |
| result = ERR_PTR(-ENOMEM); |
| goto out; |
| } |
| for (i = 1; i < num_fences; i++) { |
| tmp = result; |
| result = dma_fence_unwrap_merge(tmp, fences[i]); |
| dma_fence_put(tmp); |
| if (!result) { |
| result = ERR_PTR(-ENOMEM); |
| goto out; |
| } |
| } |
| |
| out: |
| for (i = 0; i < num_fences; i++) |
| dma_fence_put(fences[i]); |
| kfree(fences); |
| return result; |
| } |
| |
| void gcip_dma_fence_array_disable_signaling(struct dma_fence *fence) |
| { |
| struct dma_fence_array *array = container_of(fence, struct dma_fence_array, base); |
| struct dma_fence_array_cb *cb = (void *)(&array[1]); |
| unsigned long flags; |
| int i; |
| |
| if (!dma_fence_is_array(fence)) |
| return; |
| |
| spin_lock_irqsave(fence->lock, flags); |
| |
| if (!test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags)) |
| goto out; |
| |
| for (i = 0; i < array->num_fences; ++i) { |
| if (dma_fence_remove_callback(array->fences[i], &cb[i].cb)) |
| dma_fence_put(&array->base); |
| } |
| |
| clear_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags); |
| |
| out: |
| spin_unlock_irqrestore(fence->lock, flags); |
| } |