485 lines · 11.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Sync File validation framework * * Copyright (C) 2012 Google, Inc. */ #include <linux/file.h>#include <linux/fs.h>#include <linux/uaccess.h>#include <linux/panic.h>#include <linux/slab.h>#include <linux/sync_file.h> #include "sync_debug.h" #define CREATE_TRACE_POINTS#include "sync_trace.h" /* * SW SYNC validation framework * * A sync object driver that uses a 32bit counter to coordinate * synchronization.  Useful when there is no hardware primitive backing * the synchronization. * * To start the framework just open: * * <debugfs>/sync/sw_sync * * That will create a sync timeline, all fences created under this timeline * file descriptor will belong to the this timeline. * * The 'sw_sync' file can be opened many times as to create different * timelines. * * Fences can be created with SW_SYNC_IOC_CREATE_FENCE ioctl with struct * sw_sync_create_fence_data as parameter. * * To increment the timeline counter, SW_SYNC_IOC_INC ioctl should be used * with the increment as u32. This will update the last signaled value * from the timeline and signal any fence that has a seqno smaller or equal * to it. * * struct sw_sync_create_fence_data * @value:	the seqno to initialise the fence with * @name:	the name of the new sync point * @fence:	return the fd of the new sync_file with the created fence */struct sw_sync_create_fence_data {	__u32	value;	char	name[32];	__s32	fence; /* fd of new fence */}; /** * struct sw_sync_get_deadline - get the deadline hint of a sw_sync fence * @deadline_ns: absolute time of the deadline * @pad:	must be zero * @fence_fd:	the sw_sync fence fd (in) * * Return the earliest deadline set on the fence.  The timebase for the * deadline is CLOCK_MONOTONIC (same as vblank).  If there is no deadline * set on the fence, this ioctl will return -ENOENT. */struct sw_sync_get_deadline {	__u64	deadline_ns;	__u32	pad;	__s32	fence_fd;}; #define SW_SYNC_IOC_MAGIC	'W' #define SW_SYNC_IOC_CREATE_FENCE	_IOWR(SW_SYNC_IOC_MAGIC, 0,\		struct sw_sync_create_fence_data) #define SW_SYNC_IOC_INC			_IOW(SW_SYNC_IOC_MAGIC, 1, __u32)#define SW_SYNC_GET_DEADLINE		_IOWR(SW_SYNC_IOC_MAGIC, 2, \		struct sw_sync_get_deadline)  #define SW_SYNC_HAS_DEADLINE_BIT	DMA_FENCE_FLAG_USER_BITS static const struct dma_fence_ops timeline_fence_ops; static inline struct sync_pt *dma_fence_to_sync_pt(struct dma_fence *fence){	if (fence->ops != &timeline_fence_ops)		return NULL;	return container_of(fence, struct sync_pt, base);} /** * sync_timeline_create() - creates a sync object * @name:	sync_timeline name * * Creates a new sync_timeline. Returns the sync_timeline object or NULL in * case of error. */static struct sync_timeline *sync_timeline_create(const char *name){	struct sync_timeline *obj; 	obj = kzalloc_obj(*obj);	if (!obj)		return NULL; 	kref_init(&obj->kref);	obj->context = dma_fence_context_alloc(1);	strscpy(obj->name, name, sizeof(obj->name)); 	obj->pt_tree = RB_ROOT;	INIT_LIST_HEAD(&obj->pt_list);	spin_lock_init(&obj->lock); 	sync_timeline_debug_add(obj); 	return obj;} static void sync_timeline_free(struct kref *kref){	struct sync_timeline *obj =		container_of(kref, struct sync_timeline, kref); 	sync_timeline_debug_remove(obj); 	kfree(obj);} static void sync_timeline_get(struct sync_timeline *obj){	kref_get(&obj->kref);} static void sync_timeline_put(struct sync_timeline *obj){	kref_put(&obj->kref, sync_timeline_free);} static const char *timeline_fence_get_driver_name(struct dma_fence *fence){	return "sw_sync";} static const char *timeline_fence_get_timeline_name(struct dma_fence *fence){	struct sync_timeline *parent = dma_fence_parent(fence); 	return parent->name;} static void timeline_fence_release(struct dma_fence *fence){	struct sync_pt *pt = dma_fence_to_sync_pt(fence);	struct sync_timeline *parent = dma_fence_parent(fence);	unsigned long flags; 	dma_fence_lock_irqsave(fence, flags);	if (!list_empty(&pt->link)) {		list_del(&pt->link);		rb_erase(&pt->node, &parent->pt_tree);	}	dma_fence_unlock_irqrestore(fence, flags); 	sync_timeline_put(parent);	dma_fence_free(fence);} static bool timeline_fence_signaled(struct dma_fence *fence){	struct sync_timeline *parent = dma_fence_parent(fence); 	return !__dma_fence_is_later(fence, fence->seqno, parent->value);} static void timeline_fence_set_deadline(struct dma_fence *fence, ktime_t deadline){	struct sync_pt *pt = dma_fence_to_sync_pt(fence);	unsigned long flags; 	dma_fence_lock_irqsave(fence, flags);	if (test_bit(SW_SYNC_HAS_DEADLINE_BIT, &fence->flags)) {		if (ktime_before(deadline, pt->deadline))			pt->deadline = deadline;	} else {		pt->deadline = deadline;		__set_bit(SW_SYNC_HAS_DEADLINE_BIT, &fence->flags);	}	dma_fence_unlock_irqrestore(fence, flags);} static const struct dma_fence_ops timeline_fence_ops = {	.get_driver_name = timeline_fence_get_driver_name,	.get_timeline_name = timeline_fence_get_timeline_name,	.signaled = timeline_fence_signaled,	.release = timeline_fence_release,	.set_deadline = timeline_fence_set_deadline,}; /** * sync_timeline_signal() - signal a status change on a sync_timeline * @obj:	sync_timeline to signal * @inc:	num to increment on timeline->value * * A sync implementation should call this any time one of it's fences * has signaled or has an error condition. */static void sync_timeline_signal(struct sync_timeline *obj, unsigned int inc){	LIST_HEAD(signalled);	struct sync_pt *pt, *next; 	trace_sync_timeline(obj); 	spin_lock_irq(&obj->lock); 	obj->value += inc; 	list_for_each_entry_safe(pt, next, &obj->pt_list, link) {		if (!timeline_fence_signaled(&pt->base))			break; 		dma_fence_get(&pt->base); 		list_move_tail(&pt->link, &signalled);		rb_erase(&pt->node, &obj->pt_tree); 		dma_fence_signal_locked(&pt->base);	} 	spin_unlock_irq(&obj->lock); 	list_for_each_entry_safe(pt, next, &signalled, link) {		list_del_init(&pt->link);		dma_fence_put(&pt->base);	}} /** * sync_pt_create() - creates a sync pt * @obj:	parent sync_timeline * @value:	value of the fence * * Creates a new sync_pt (fence) as a child of @parent.  @size bytes will be * allocated allowing for implementation specific data to be kept after * the generic sync_timeline struct. Returns the sync_pt object or * NULL in case of error. */static struct sync_pt *sync_pt_create(struct sync_timeline *obj,				      unsigned int value){	struct sync_pt *pt; 	pt = kzalloc_obj(*pt);	if (!pt)		return NULL; 	sync_timeline_get(obj);	dma_fence_init(&pt->base, &timeline_fence_ops, &obj->lock,		       obj->context, value);	INIT_LIST_HEAD(&pt->link); 	spin_lock_irq(&obj->lock);	if (!dma_fence_is_signaled_locked(&pt->base)) {		struct rb_node **p = &obj->pt_tree.rb_node;		struct rb_node *parent = NULL; 		while (*p) {			struct sync_pt *other;			int cmp; 			parent = *p;			other = rb_entry(parent, typeof(*pt), node);			cmp = value - other->base.seqno;			if (cmp > 0) {				p = &parent->rb_right;			} else if (cmp < 0) {				p = &parent->rb_left;			} else {				if (dma_fence_get_rcu(&other->base)) {					sync_timeline_put(obj);					kfree(pt);					pt = other;					goto unlock;				}				p = &parent->rb_left;			}		}		rb_link_node(&pt->node, parent, p);		rb_insert_color(&pt->node, &obj->pt_tree); 		parent = rb_next(&pt->node);		list_add_tail(&pt->link,			      parent ? &rb_entry(parent, typeof(*pt), node)->link : &obj->pt_list);	}unlock:	spin_unlock_irq(&obj->lock); 	return pt;} /* * *WARNING* * * improper use of this can result in deadlocking kernel drivers from userspace. */ /* opening sw_sync create a new sync obj */static int sw_sync_debugfs_open(struct inode *inode, struct file *file){	struct sync_timeline *obj;	char task_comm[TASK_COMM_LEN]; 	get_task_comm(task_comm, current); 	obj = sync_timeline_create(task_comm);	if (!obj)		return -ENOMEM; 	file->private_data = obj; 	return 0;} static int sw_sync_debugfs_release(struct inode *inode, struct file *file){	struct sync_timeline *obj = file->private_data;	struct sync_pt *pt, *next; 	spin_lock_irq(&obj->lock); 	list_for_each_entry_safe(pt, next, &obj->pt_list, link) {		dma_fence_set_error(&pt->base, -ENOENT);		dma_fence_signal_locked(&pt->base);	} 	spin_unlock_irq(&obj->lock); 	sync_timeline_put(obj);	return 0;} static long sw_sync_ioctl_create_fence(struct sync_timeline *obj,				       unsigned long arg){	int fd = get_unused_fd_flags(O_CLOEXEC);	int err;	struct sync_pt *pt;	struct sync_file *sync_file;	struct sw_sync_create_fence_data data; 	/* SW sync fence are inherently unsafe and can deadlock the kernel */	add_taint(TAINT_SOFTLOCKUP, LOCKDEP_STILL_OK); 	if (fd < 0)		return fd; 	if (copy_from_user(&data, (void __user *)arg, sizeof(data))) {		err = -EFAULT;		goto err;	} 	pt = sync_pt_create(obj, data.value);	if (!pt) {		err = -ENOMEM;		goto err;	} 	sync_file = sync_file_create(&pt->base);	dma_fence_put(&pt->base);	if (!sync_file) {		err = -ENOMEM;		goto err;	} 	data.fence = fd;	if (copy_to_user((void __user *)arg, &data, sizeof(data))) {		fput(sync_file->file);		err = -EFAULT;		goto err;	} 	fd_install(fd, sync_file->file); 	return 0; err:	put_unused_fd(fd);	return err;} static long sw_sync_ioctl_inc(struct sync_timeline *obj, unsigned long arg){	u32 value; 	if (copy_from_user(&value, (void __user *)arg, sizeof(value)))		return -EFAULT; 	while (value > INT_MAX)  {		sync_timeline_signal(obj, INT_MAX);		value -= INT_MAX;	} 	sync_timeline_signal(obj, value); 	return 0;} static int sw_sync_ioctl_get_deadline(struct sync_timeline *obj, unsigned long arg){	struct sw_sync_get_deadline data;	struct dma_fence *fence;	unsigned long flags;	struct sync_pt *pt;	int ret = 0; 	if (copy_from_user(&data, (void __user *)arg, sizeof(data)))		return -EFAULT; 	if (data.deadline_ns || data.pad)		return -EINVAL; 	fence = sync_file_get_fence(data.fence_fd);	if (!fence)		return -EINVAL; 	pt = dma_fence_to_sync_pt(fence);	if (!pt) {		ret = -EINVAL;		goto put_fence;	} 	dma_fence_lock_irqsave(fence, flags);	if (!test_bit(SW_SYNC_HAS_DEADLINE_BIT, &fence->flags)) {		ret = -ENOENT;		goto unlock;	}	data.deadline_ns = ktime_to_ns(pt->deadline);	dma_fence_unlock_irqrestore(fence, flags); 	dma_fence_put(fence); 	if (ret)		return ret; 	if (copy_to_user((void __user *)arg, &data, sizeof(data)))		return -EFAULT; 	return 0; unlock:	dma_fence_unlock_irqrestore(fence, flags);put_fence:	dma_fence_put(fence); 	return ret;} static long sw_sync_ioctl(struct file *file, unsigned int cmd,			  unsigned long arg){	struct sync_timeline *obj = file->private_data; 	switch (cmd) {	case SW_SYNC_IOC_CREATE_FENCE:		return sw_sync_ioctl_create_fence(obj, arg); 	case SW_SYNC_IOC_INC:		return sw_sync_ioctl_inc(obj, arg); 	case SW_SYNC_GET_DEADLINE:		return sw_sync_ioctl_get_deadline(obj, arg); 	default:		return -ENOTTY;	}} const struct file_operations sw_sync_debugfs_fops = {	.open           = sw_sync_debugfs_open,	.release        = sw_sync_debugfs_release,	.unlocked_ioctl = sw_sync_ioctl,	.compat_ioctl	= compat_ptr_ioctl,};