/* * drivers/video/tegra/host/nvhost_sync.c * * Tegra Graphics Host Syncpoint Integration to linux/sync Framework * * Copyright (c) 2013-2020, NVIDIA Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "nvhost_sync.h" #include "nvhost_syncpt.h" #include "nvhost_intr.h" #include "nvhost_acm.h" #include "dev.h" #include "chip_support.h" struct nvhost_sync_timeline { struct sync_timeline obj; struct nvhost_syncpt *sp; u32 id; }; /** * The sync framework dups pts when merging fences. We share a single * refcounted nvhost_sync_pt for each duped pt. */ struct nvhost_sync_pt { struct kref refcount; u32 thresh; bool has_intr; struct nvhost_sync_timeline *obj; }; struct nvhost_sync_pt_inst { struct sync_pt pt; struct nvhost_sync_pt *shared; }; static struct nvhost_sync_pt *to_nvhost_sync_pt(struct sync_pt *pt) { struct nvhost_sync_pt_inst *pti = container_of(pt, struct nvhost_sync_pt_inst, pt); return pti->shared; } static void nvhost_sync_pt_free_shared(struct kref *ref) { struct nvhost_sync_pt *pt = container_of(ref, struct nvhost_sync_pt, refcount); /* Host should have been idled in nvhost_sync_pt_signal. */ if (pt->has_intr) pr_err("%s: BUG! Host not idle, free'ing syncpt! id=%u thresh=%u\n", __func__, pt->obj->id, pt->thresh); kfree(pt); } /* Request an interrupt to signal the timeline on thresh. */ static int nvhost_sync_pt_set_intr(struct nvhost_sync_pt *pt) { int err; void *waiter; /** * When this syncpoint expires, we must call sync_timeline_signal. * That requires us to schedule an interrupt at this point, even * though we might never end up doing a CPU wait on the syncpoint. * Most of the time this does not hurt us since we have already set * an interrupt for SUBMIT_COMPLETE on the same syncpt value. */ /* Get a ref for the interrupt handler, keep host alive. */ kref_get(&pt->refcount); pt->has_intr = true; err = nvhost_module_busy(syncpt_to_dev(pt->obj->sp)->dev); if (err) { pt->has_intr = false; kref_put(&pt->refcount, nvhost_sync_pt_free_shared); return err; } waiter = nvhost_intr_alloc_waiter(); err = nvhost_intr_add_action(&(syncpt_to_dev(pt->obj->sp)->intr), pt->obj->id, pt->thresh, NVHOST_INTR_ACTION_SIGNAL_SYNC_PT, pt, waiter, NULL); if (err) { nvhost_module_idle(syncpt_to_dev(pt->obj->sp)->dev); pt->has_intr = false; kref_put(&pt->refcount, nvhost_sync_pt_free_shared); return err; } return 0; } static struct nvhost_sync_pt *nvhost_sync_pt_create_shared( struct nvhost_sync_timeline *obj, u32 thresh) { struct nvhost_sync_pt *shared; shared = kzalloc(sizeof(*shared), GFP_KERNEL); if (!shared) return NULL; kref_init(&shared->refcount); shared->obj = obj; shared->thresh = thresh; shared->has_intr = false; if ((obj->id != NVSYNCPT_INVALID) && !nvhost_syncpt_is_expired(obj->sp, obj->id, thresh)) { if (nvhost_sync_pt_set_intr(shared)) { kfree(shared); return NULL; } } return shared; } static struct sync_pt *nvhost_sync_pt_create_inst( struct nvhost_sync_timeline *obj, u32 thresh) { struct nvhost_sync_pt_inst *pti; pti = (struct nvhost_sync_pt_inst *) sync_pt_create(&obj->obj, sizeof(*pti)); if (!pti) return NULL; pti->shared = nvhost_sync_pt_create_shared(obj, thresh); if (!pti->shared) { sync_pt_free(&pti->pt); return NULL; } return &pti->pt; } static void nvhost_sync_pt_free_inst(struct sync_pt *sync_pt) { struct nvhost_sync_pt *pt = to_nvhost_sync_pt(sync_pt); if (pt) kref_put(&pt->refcount, nvhost_sync_pt_free_shared); } static struct sync_pt *nvhost_sync_pt_dup_inst(struct sync_pt *sync_pt) { struct nvhost_sync_pt_inst *pti; struct nvhost_sync_pt *pt = to_nvhost_sync_pt(sync_pt); pti = (struct nvhost_sync_pt_inst *) sync_pt_create(&pt->obj->obj, sizeof(*pti)); if (!pti) return NULL; pti->shared = pt; kref_get(&pt->refcount); return &pti->pt; } static int nvhost_sync_pt_has_signaled(struct sync_pt *sync_pt) { struct nvhost_sync_pt *pt = to_nvhost_sync_pt(sync_pt); struct nvhost_sync_timeline *obj; /* shared data may not be available yet */ if (!pt) return 0; obj = pt->obj; if (obj->id != NVSYNCPT_INVALID) /* No need to update min */ return nvhost_syncpt_is_expired(obj->sp, obj->id, pt->thresh); else return 1; } static int nvhost_sync_pt_compare(struct sync_pt *a, struct sync_pt *b) { struct nvhost_sync_pt *pt_a = to_nvhost_sync_pt(a); struct nvhost_sync_pt *pt_b = to_nvhost_sync_pt(b); struct nvhost_sync_timeline *obj = pt_a->obj; if (pt_a->obj != pt_b->obj) { pr_err("%s: Sync timeline missmatch! ida=%u idb=%u\n", __func__, pt_a->obj->id, pt_b->obj->id); WARN_ON(1); return 0; } if (obj->id != NVSYNCPT_INVALID) /* No need to update min */ return nvhost_syncpt_compare(obj->sp, obj->id, pt_a->thresh, pt_b->thresh); else return 0; } static u32 nvhost_sync_timeline_current(struct nvhost_sync_timeline *obj) { if (obj->id != NVSYNCPT_INVALID) return nvhost_syncpt_read_min(obj->sp, obj->id); else return 0; } static void nvhost_sync_timeline_value_str(struct sync_timeline *timeline, char *str, int size) { struct nvhost_sync_timeline *obj = (struct nvhost_sync_timeline *)timeline; snprintf(str, size, "%d", nvhost_sync_timeline_current(obj)); } static void nvhost_sync_pt_value_str(struct sync_pt *sync_pt, char *str, int size) { struct nvhost_sync_pt *pt = to_nvhost_sync_pt(sync_pt); struct nvhost_sync_timeline *obj; /* shared data may not be available yet */ if (!pt) { snprintf(str, size, "NA"); return; } obj = pt->obj; if (obj->id != NVSYNCPT_INVALID) snprintf(str, size, "%d", pt->thresh); else snprintf(str, size, "0"); } static void nvhost_sync_get_pt_name(struct sync_pt *sync_pt, char *str, int size) { struct nvhost_sync_pt *pt = to_nvhost_sync_pt(sync_pt); struct nvhost_sync_timeline *obj; /* shared data may not be available yet */ if (!pt) { snprintf(str, size, "NA"); return; } obj = pt->obj; if (obj->id != NVSYNCPT_INVALID) snprintf(str, size, "%s", nvhost_syncpt_get_name_from_id(obj->sp, obj->id)); else snprintf(str, size, "0"); } static int nvhost_sync_fill_driver_data(struct sync_pt *sync_pt, void *data, int size) { struct nvhost_sync_pt *pt = to_nvhost_sync_pt(sync_pt); struct nvhost_ctrl_sync_fence_info info; if (size < sizeof(info)) { nvhost_err(NULL, "size %d too small", size); return -ENOMEM; } info.id = pt->obj->id; info.thresh = pt->thresh; memcpy(data, &info, sizeof(info)); return sizeof(info); } static void nvhost_sync_platform_debug_dump(struct sync_pt *pt) { struct nvhost_sync_pt *__pt = to_nvhost_sync_pt(pt); struct nvhost_sync_timeline *obj = __pt->obj; struct nvhost_syncpt *sp = obj->sp; nvhost_debug_dump(syncpt_to_dev(sp)); } static const struct sync_timeline_ops nvhost_sync_timeline_ops = { .driver_name = "nvhost_sync", .dup = nvhost_sync_pt_dup_inst, .has_signaled = nvhost_sync_pt_has_signaled, .compare = nvhost_sync_pt_compare, .free_pt = nvhost_sync_pt_free_inst, .fill_driver_data = nvhost_sync_fill_driver_data, .timeline_value_str = nvhost_sync_timeline_value_str, .pt_value_str = nvhost_sync_pt_value_str, .get_pt_name = nvhost_sync_get_pt_name, .platform_debug_dump = nvhost_sync_platform_debug_dump }; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) struct sync_fence *nvhost_sync_fdget(int fd) { struct sync_fence *fence = sync_fence_fdget(fd); struct sync_pt *spt; struct sync_timeline *t; if (!fence) return fence; list_for_each_entry(spt, &fence->pt_list_head, pt_list) { t = spt->parent; if (t->ops != &nvhost_sync_timeline_ops) { sync_fence_put(fence); return NULL; } } return fence; } EXPORT_SYMBOL(nvhost_sync_fdget); int nvhost_sync_num_pts(struct sync_fence *fence) { int num = 0; struct list_head *pos; list_for_each(pos, &fence->pt_list_head) { num++; } return num; } EXPORT_SYMBOL(nvhost_sync_num_pts); #else /* LINUX_VERSION_CODE */ struct sync_fence *nvhost_sync_fdget(int fd) { struct sync_fence *fence = sync_fence_fdget(fd); int i; if (!fence) return fence; for (i = 0; i < fence->num_fences; i++) { #if LINUX_VERSION_CODE > KERNEL_VERSION(4, 13, 0) struct dma_fence *pt = fence->cbs[i].sync_pt; #else struct fence *pt = fence->cbs[i].sync_pt; #endif struct sync_pt *spt = sync_pt_from_fence(pt); struct sync_timeline *t; if (spt == NULL) { sync_fence_put(fence); return NULL; } t = sync_pt_parent(spt); if (t->ops != &nvhost_sync_timeline_ops) { sync_fence_put(fence); return NULL; } } return fence; } EXPORT_SYMBOL(nvhost_sync_fdget); int nvhost_sync_num_pts(struct sync_fence *fence) { return fence->num_fences; } EXPORT_SYMBOL(nvhost_sync_num_pts); #endif /* end if LINUX_VERSION_CODE */ u32 nvhost_sync_pt_id(struct sync_pt *__pt) { struct nvhost_sync_pt *pt = to_nvhost_sync_pt(__pt); return pt->obj->id; } EXPORT_SYMBOL(nvhost_sync_pt_id); u32 nvhost_sync_pt_thresh(struct sync_pt *__pt) { struct nvhost_sync_pt *pt = to_nvhost_sync_pt(__pt); return pt->thresh; } EXPORT_SYMBOL(nvhost_sync_pt_thresh); /* Public API */ struct nvhost_sync_timeline *nvhost_sync_timeline_create( struct nvhost_syncpt *sp, int id) { struct nvhost_sync_timeline *obj; char name[30]; const char *syncpt_name = NULL; if (id != NVSYNCPT_INVALID) syncpt_name = syncpt_op().name(sp, id); if (syncpt_name && strlen(syncpt_name)) snprintf(name, sizeof(name), "%d_%s", id, syncpt_name); else snprintf(name, sizeof(name), "%d", id); obj = (struct nvhost_sync_timeline *) sync_timeline_create(&nvhost_sync_timeline_ops, sizeof(struct nvhost_sync_timeline), name); if (!obj) return NULL; obj->sp = sp; obj->id = id; return obj; } void nvhost_sync_pt_signal(struct nvhost_sync_pt *pt, u64 timestamp) { /* At this point the fence (and its sync_pt's) might already be gone if * the user has closed its fd's. The nvhost_sync_pt object still exists * since we took a ref while scheduling the interrupt. */ struct nvhost_sync_timeline *obj = pt->obj; if (pt->has_intr) { nvhost_module_idle(syncpt_to_dev(pt->obj->sp)->dev); pt->has_intr = false; kref_put(&pt->refcount, nvhost_sync_pt_free_shared); } sync_timeline_signal(&obj->obj, timestamp); } int nvhost_sync_fence_set_name(int fence_fd, const char *name) { struct sync_fence *fence = nvhost_sync_fdget(fence_fd); if (!fence) { nvhost_err(NULL, "failed to get fence"); return -EINVAL; } strlcpy(fence->name, name, sizeof(fence->name)); sync_fence_put(fence); return 0; } EXPORT_SYMBOL(nvhost_sync_fence_set_name); int nvhost_sync_create_fence_fd(struct platform_device *pdev, struct nvhost_ctrl_sync_fence_info *pts, u32 num_pts, const char *name, int *fence_fd) { int fd; struct sync_fence *fence = NULL; fence = nvhost_sync_create_fence(pdev, pts, num_pts, name); if (IS_ERR(fence)) return -EINVAL; fd = get_unused_fd_flags(O_CLOEXEC); if (fd < 0) { sync_fence_put(fence); return fd; } *fence_fd = fd; sync_fence_install(fence, fd); return 0; } EXPORT_SYMBOL(nvhost_sync_create_fence_fd); struct sync_fence *nvhost_sync_create_fence(struct platform_device *pdev, struct nvhost_ctrl_sync_fence_info *pts, u32 num_pts, const char *name) { struct nvhost_master *master = nvhost_get_host(pdev); struct nvhost_syncpt *sp = &master->syncpt; int err; u32 i; struct sync_fence *fence = NULL; for (i = 0; i < num_pts; i++) { if (!nvhost_syncpt_is_valid_hw_pt(sp, pts[i].id)) { nvhost_err(&pdev->dev, "invalid syncpoint id %u", pts[i].id); WARN_ON(1); return ERR_PTR(-EINVAL); } pts[i].id = array_index_nospec(pts[i].id, nvhost_syncpt_nb_hw_pts(sp)); } for (i = 0; i < num_pts; i++) { struct nvhost_sync_timeline *obj; struct sync_pt *pt; struct sync_fence *f, *f2; u32 id = pts[i].id; u32 thresh = pts[i].thresh; obj = nvhost_syncpt_timeline(sp, id); pt = nvhost_sync_pt_create_inst(obj, thresh); if (pt == NULL) { err = -ENOMEM; goto err; } f = sync_fence_create(name, pt); if (f == NULL) { sync_pt_free(pt); err = -ENOMEM; goto err; } if (fence == NULL) { fence = f; } else { f2 = sync_fence_merge(name, fence, f); sync_fence_put(f); sync_fence_put(fence); fence = f2; if (!fence) { err = -ENOMEM; goto err; } } } if (fence == NULL) { err = -EINVAL; goto err; } return fence; err: if (fence) sync_fence_put(fence); return ERR_PTR(err); } EXPORT_SYMBOL(nvhost_sync_create_fence);