1017 lines · 22.5 KB · C
RawDownload
/* * drivers/base/sync.c * * Copyright (C) 2012 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 <linux/debugfs.h>#include <linux/export.h>#include <linux/file.h>#include <linux/fs.h>#include <linux/kernel.h>#include <linux/poll.h>#include <linux/sched.h>#include <linux/seq_file.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/anon_inodes.h> #include "sync.h" #define CREATE_TRACE_POINTS#include "trace/sync.h" static void sync_fence_signal_pt(struct sync_pt *pt);static int _sync_pt_has_signaled(struct sync_pt *pt);static void sync_fence_free(struct kref *kref);static void sync_dump(void); static LIST_HEAD(sync_timeline_list_head);static DEFINE_SPINLOCK(sync_timeline_list_lock); static LIST_HEAD(sync_fence_list_head);static DEFINE_SPINLOCK(sync_fence_list_lock); struct sync_timeline *sync_timeline_create(const struct sync_timeline_ops *ops,					   int size, const char *name){	struct sync_timeline *obj;	unsigned long flags; 	if (size < sizeof(struct sync_timeline))		return NULL; 	obj = kzalloc(size, GFP_KERNEL);	if (obj == NULL)		return NULL; 	kref_init(&obj->kref);	obj->ops = ops;	strlcpy(obj->name, name, sizeof(obj->name)); 	INIT_LIST_HEAD(&obj->child_list_head);	spin_lock_init(&obj->child_list_lock); 	INIT_LIST_HEAD(&obj->active_list_head);	spin_lock_init(&obj->active_list_lock); 	spin_lock_irqsave(&sync_timeline_list_lock, flags);	list_add_tail(&obj->sync_timeline_list, &sync_timeline_list_head);	spin_unlock_irqrestore(&sync_timeline_list_lock, flags); 	return obj;}EXPORT_SYMBOL(sync_timeline_create); static void sync_timeline_free(struct kref *kref){	struct sync_timeline *obj =		container_of(kref, struct sync_timeline, kref);	unsigned long flags; 	spin_lock_irqsave(&sync_timeline_list_lock, flags);	list_del(&obj->sync_timeline_list);	spin_unlock_irqrestore(&sync_timeline_list_lock, flags); 	if (obj->ops->release_obj)		obj->ops->release_obj(obj); 	kfree(obj);} void sync_timeline_destroy(struct sync_timeline *obj){	obj->destroyed = true;	smp_wmb(); 	/*	 * signal any children that their parent is going away.	 */	sync_timeline_signal(obj); 	kref_put(&obj->kref, sync_timeline_free);}EXPORT_SYMBOL(sync_timeline_destroy); static void sync_timeline_add_pt(struct sync_timeline *obj, struct sync_pt *pt){	unsigned long flags; 	pt->parent = obj; 	spin_lock_irqsave(&obj->child_list_lock, flags);	list_add_tail(&pt->child_list, &obj->child_list_head);	spin_unlock_irqrestore(&obj->child_list_lock, flags);} static void sync_timeline_remove_pt(struct sync_pt *pt){	struct sync_timeline *obj = pt->parent;	unsigned long flags; 	spin_lock_irqsave(&obj->active_list_lock, flags);	if (!list_empty(&pt->active_list))		list_del_init(&pt->active_list);	spin_unlock_irqrestore(&obj->active_list_lock, flags); 	spin_lock_irqsave(&obj->child_list_lock, flags);	if (!list_empty(&pt->child_list))		list_del_init(&pt->child_list); 	spin_unlock_irqrestore(&obj->child_list_lock, flags);} void sync_timeline_signal(struct sync_timeline *obj){	unsigned long flags;	LIST_HEAD(signaled_pts);	struct list_head *pos, *n; 	trace_sync_timeline(obj); 	spin_lock_irqsave(&obj->active_list_lock, flags); 	list_for_each_safe(pos, n, &obj->active_list_head) {		struct sync_pt *pt =			container_of(pos, struct sync_pt, active_list); 		if (_sync_pt_has_signaled(pt)) {			list_del_init(pos);			list_add(&pt->signaled_list, &signaled_pts);			kref_get(&pt->fence->kref);		}	} 	spin_unlock_irqrestore(&obj->active_list_lock, flags); 	list_for_each_safe(pos, n, &signaled_pts) {		struct sync_pt *pt =			container_of(pos, struct sync_pt, signaled_list); 		list_del_init(pos);		sync_fence_signal_pt(pt);		kref_put(&pt->fence->kref, sync_fence_free);	}}EXPORT_SYMBOL(sync_timeline_signal); struct sync_pt *sync_pt_create(struct sync_timeline *parent, int size){	struct sync_pt *pt; 	if (size < sizeof(struct sync_pt))		return NULL; 	pt = kzalloc(size, GFP_KERNEL);	if (pt == NULL)		return NULL; 	INIT_LIST_HEAD(&pt->active_list);	kref_get(&parent->kref);	sync_timeline_add_pt(parent, pt); 	return pt;}EXPORT_SYMBOL(sync_pt_create); void sync_pt_free(struct sync_pt *pt){	if (pt->parent->ops->free_pt)		pt->parent->ops->free_pt(pt); 	sync_timeline_remove_pt(pt); 	kref_put(&pt->parent->kref, sync_timeline_free); 	kfree(pt);}EXPORT_SYMBOL(sync_pt_free); /* call with pt->parent->active_list_lock held */static int _sync_pt_has_signaled(struct sync_pt *pt){	int old_status = pt->status; 	if (!pt->status)		pt->status = pt->parent->ops->has_signaled(pt); 	if (!pt->status && pt->parent->destroyed)		pt->status = -ENOENT; 	if (pt->status != old_status)		pt->timestamp = ktime_get(); 	return pt->status;} static struct sync_pt *sync_pt_dup(struct sync_pt *pt){	return pt->parent->ops->dup(pt);} /* Adds a sync pt to the active queue.  Called when added to a fence */static void sync_pt_activate(struct sync_pt *pt){	struct sync_timeline *obj = pt->parent;	unsigned long flags;	int err; 	spin_lock_irqsave(&obj->active_list_lock, flags); 	err = _sync_pt_has_signaled(pt);	if (err != 0)		goto out; 	list_add_tail(&pt->active_list, &obj->active_list_head); out:	spin_unlock_irqrestore(&obj->active_list_lock, flags);} static int sync_fence_release(struct inode *inode, struct file *file);static unsigned int sync_fence_poll(struct file *file, poll_table *wait);static long sync_fence_ioctl(struct file *file, unsigned int cmd,			     unsigned long arg);  static const struct file_operations sync_fence_fops = {	.release = sync_fence_release,	.poll = sync_fence_poll,	.unlocked_ioctl = sync_fence_ioctl,	.compat_ioctl = sync_fence_ioctl,}; static struct sync_fence *sync_fence_alloc(const char *name){	struct sync_fence *fence;	unsigned long flags; 	fence = kzalloc(sizeof(struct sync_fence), GFP_KERNEL);	if (fence == NULL)		return NULL; 	fence->file = anon_inode_getfile("sync_fence", &sync_fence_fops,					 fence, 0);	if (IS_ERR(fence->file))		goto err; 	kref_init(&fence->kref);	strlcpy(fence->name, name, sizeof(fence->name)); 	INIT_LIST_HEAD(&fence->pt_list_head);	INIT_LIST_HEAD(&fence->waiter_list_head);	spin_lock_init(&fence->waiter_list_lock); 	init_waitqueue_head(&fence->wq); 	spin_lock_irqsave(&sync_fence_list_lock, flags);	list_add_tail(&fence->sync_fence_list, &sync_fence_list_head);	spin_unlock_irqrestore(&sync_fence_list_lock, flags); 	return fence; err:	kfree(fence);	return NULL;} /* TODO: implement a create which takes more that one sync_pt */struct sync_fence *sync_fence_create(const char *name, struct sync_pt *pt){	struct sync_fence *fence; 	if (pt->fence)		return NULL; 	fence = sync_fence_alloc(name);	if (fence == NULL)		return NULL; 	pt->fence = fence;	list_add(&pt->pt_list, &fence->pt_list_head);	sync_pt_activate(pt); 	/*	 * signal the fence in case pt was activated before	 * sync_pt_activate(pt) was called	 */	sync_fence_signal_pt(pt); 	return fence;}EXPORT_SYMBOL(sync_fence_create); static int sync_fence_copy_pts(struct sync_fence *dst, struct sync_fence *src){	struct list_head *pos; 	list_for_each(pos, &src->pt_list_head) {		struct sync_pt *orig_pt =			container_of(pos, struct sync_pt, pt_list);		struct sync_pt *new_pt = sync_pt_dup(orig_pt); 		if (new_pt == NULL)			return -ENOMEM; 		new_pt->fence = dst;		list_add(&new_pt->pt_list, &dst->pt_list_head);	} 	return 0;} static int sync_fence_merge_pts(struct sync_fence *dst, struct sync_fence *src){	struct list_head *src_pos, *dst_pos, *n; 	list_for_each(src_pos, &src->pt_list_head) {		struct sync_pt *src_pt =			container_of(src_pos, struct sync_pt, pt_list);		bool collapsed = false; 		list_for_each_safe(dst_pos, n, &dst->pt_list_head) {			struct sync_pt *dst_pt =				container_of(dst_pos, struct sync_pt, pt_list);			/* collapse two sync_pts on the same timeline			 * to a single sync_pt that will signal at			 * the later of the two			 */			if (dst_pt->parent == src_pt->parent) {				if (dst_pt->parent->ops->compare(dst_pt, src_pt)						 == -1) {					struct sync_pt *new_pt =						sync_pt_dup(src_pt);					if (new_pt == NULL)						return -ENOMEM; 					new_pt->fence = dst;					list_replace(&dst_pt->pt_list,						     &new_pt->pt_list);					sync_pt_free(dst_pt);				}				collapsed = true;				break;			}		} 		if (!collapsed) {			struct sync_pt *new_pt = sync_pt_dup(src_pt); 			if (new_pt == NULL)				return -ENOMEM; 			new_pt->fence = dst;			list_add(&new_pt->pt_list, &dst->pt_list_head);		}	} 	return 0;} static void sync_fence_detach_pts(struct sync_fence *fence){	struct list_head *pos, *n; 	list_for_each_safe(pos, n, &fence->pt_list_head) {		struct sync_pt *pt = container_of(pos, struct sync_pt, pt_list);		sync_timeline_remove_pt(pt);	}} static void sync_fence_free_pts(struct sync_fence *fence){	struct list_head *pos, *n; 	list_for_each_safe(pos, n, &fence->pt_list_head) {		struct sync_pt *pt = container_of(pos, struct sync_pt, pt_list);		sync_pt_free(pt);	}} struct sync_fence *sync_fence_fdget(int fd){	struct file *file = fget(fd); 	if (file == NULL)		return NULL; 	if (file->f_op != &sync_fence_fops)		goto err; 	return file->private_data; err:	fput(file);	return NULL;}EXPORT_SYMBOL(sync_fence_fdget); void sync_fence_put(struct sync_fence *fence){	fput(fence->file);}EXPORT_SYMBOL(sync_fence_put); void sync_fence_install(struct sync_fence *fence, int fd){	fd_install(fd, fence->file);}EXPORT_SYMBOL(sync_fence_install); static int sync_fence_get_status(struct sync_fence *fence){	struct list_head *pos;	int status = 1; 	list_for_each(pos, &fence->pt_list_head) {		struct sync_pt *pt = container_of(pos, struct sync_pt, pt_list);		int pt_status = pt->status; 		if (pt_status < 0) {			status = pt_status;			break;		} else if (status == 1) {			status = pt_status;		}	} 	return status;} struct sync_fence *sync_fence_merge(const char *name,				    struct sync_fence *a, struct sync_fence *b){	struct sync_fence *fence;	struct list_head *pos;	int err; 	fence = sync_fence_alloc(name);	if (fence == NULL)		return NULL; 	err = sync_fence_copy_pts(fence, a);	if (err < 0)		goto err; 	err = sync_fence_merge_pts(fence, b);	if (err < 0)		goto err; 	list_for_each(pos, &fence->pt_list_head) {		struct sync_pt *pt =			container_of(pos, struct sync_pt, pt_list);		sync_pt_activate(pt);	} 	/*	 * signal the fence in case one of it's pts were activated before	 * they were activated	 */	sync_fence_signal_pt(list_first_entry(&fence->pt_list_head,					      struct sync_pt,					      pt_list)); 	return fence;err:	sync_fence_free_pts(fence);	kfree(fence);	return NULL;}EXPORT_SYMBOL(sync_fence_merge); static void sync_fence_signal_pt(struct sync_pt *pt){	LIST_HEAD(signaled_waiters);	struct sync_fence *fence = pt->fence;	struct list_head *pos;	struct list_head *n;	unsigned long flags;	int status; 

Lines 1–500 of 1017.