dma-fence简析以及使用demo

218 篇文章 29 订阅
1 篇文章 0 订阅

dma fence是Linux中用于不同内核模块DMA同步操作的原语,常用于GPU Rending,display buffer等之间的同步,使用DMA FENCE可以减少在用户态的等待,让数据的同步在内核中进行,它是内核中一种比较常用的同步机制,本身的实现和使用并不复杂,其只有两种状态signaled和unsignaled。可能正是因为其本身的精简,在融入其他概念中时,在不同的环境下,赋予了dma-fence不同的含义。所以通常需要根据dma-fence的具体使用的情况来理解其含义。

dma-fence是内核中的同步原语,本身只能表示两种状态,这点上就和complete有点类似了。

但是dma-fence是可以跨设备,跨进程的。

具体来说:

1.就是A设备驱动程序中创建的dma-fence可以被B驱动程序使用。

2.dma-fence是由内核创建,但是可以在进程间传递,并且可以在用户层获取fence的当前状态。

而常规的内核中的同步方法,则不具备对上述两点的支持。

基本原理:

一个被初始化的dma-fence,使用wait函数后,会将当前进程换出,即当前进程会sleep,而当调用signal函数时会唤醒被wait函数换出的进程。

dma-fence的使用还可以通过向dma-fence添加一个或多个callback函数,当dma-fence调用signal操作时,会依次遍历callback list,并调用每个callback函数。当调用wait函数时,会把默认的一个callback函数加入到dma-fence中,而这个函数就起到唤醒的作用。

dma-fence在内核中被创建,可以通过导出一个文件描述符fd到user层,然后用户层可以对该fd做常规的文件操作,也可以把该fence传递给其他进程。这个fd给到内核中后,又可以还原出dma-fence的内核数据结构。所以在user层看到的dma-fence是一个文件描述符。

其中提到的几个操作对用函数如下:

  1. init:dma_fence_init()
  2. wait:dma_fence_wait()
  3. signal:dma_fence_signal()
  4. callback:dma_fence_add_callback()

dma-fence demo

demo的流程如下,本DEMO使用了两种唤醒机制,分别为POLL和fence唤醒,前者并没有使用FENCE机制同步,而是使用了驱动自己的就绪队列,后者使用了FENCE机制进行了同步,使用了FENCE对象自身的唤醒队列。

源码如下:

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include <asm/ioctl.h>
#include <asm/fcntl.h>
#include <linux/uaccess.h>
#include <linux/dma-fence.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/sync_file.h>
#include <linux/fs.h>
#include <linux/poll.h>

#define DMA_FENCE_WAIT_CMD              _IOWR('f', 0, int)
#define DMA_FENCE_EXPORT_CMD            _IOWR('f', 1, int)
#define DMA_FENCE_SIGNAL_CMD            _IO('f', 2)

static int in_fence_fd = -1;
static int out_fence_fd = -1;
static int poll_signaled = 0;

static struct dma_fence_cb cb;
static wait_queue_head_t poll_wait_head;

static DEFINE_SPINLOCK(fence_lock);

static void dma_fence_cb(struct dma_fence *f, struct dma_fence_cb *cb)
{
	//dump_stack();
	printk("dma-fence callback !.\n");
}

static const char *dma_fence_get_name(struct dma_fence *fence)
{
	return "dma-fence-example";
}

static const struct dma_fence_ops fence_ops = {
	.get_driver_name = dma_fence_get_name,
	.get_timeline_name = dma_fence_get_name,
};

static void iter_fence_callbac(struct dma_fence *fence)
{
	unsigned long flags;
	struct dma_fence_cb *cur, *tmp;

	spin_lock_irqsave(fence->lock, flags);
	list_for_each_entry_safe(cur, tmp, &fence->cb_list, node) {
		printk("%s line %d cur->func = 0x%px, 0x%pS.\n", __func__, __LINE__, cur->func, cur->func);
	}
	spin_unlock_irqrestore(fence->lock, flags);

	return;
}

static long fence_ioctl(struct file *filp,
                        unsigned int cmd, unsigned long arg)
{
	struct sync_file *sync_file;
	struct dma_fence *in_fence;
	struct dma_fence *out_fence;

	out_fence = (struct dma_fence *)filp->private_data;
	if (out_fence == NULL) {
		pr_err("%s line %d. fence is null.\n", __func__, __LINE__);
		return -1;
	}

	switch (cmd) {
	case DMA_FENCE_SIGNAL_CMD:
		if (out_fence) {
			printk("signal fence, seqno %lld.\n", out_fence->seqno);
			iter_fence_callbac(out_fence);
			dma_fence_signal(out_fence);
			wake_up_interruptible(&poll_wait_head);
			poll_signaled = 1;
		}

		break;

	case DMA_FENCE_WAIT_CMD:
		if (copy_from_user(&in_fence_fd, (void __user *)arg, sizeof(int)) != 0)
			return -EFAULT;

		in_fence = sync_file_get_fence(in_fence_fd);
		if (!in_fence)
			return -EINVAL;

		printk("Get in-fence from fd = %d, in_fence 0x%px.\n", in_fence_fd, in_fence);

		/* add a callback func */
		dma_fence_add_callback(in_fence, &cb, dma_fence_cb);

		printk("waiting in-fence seqno %lld to be signaled, process is blocking ...\n", 
				in_fence->seqno);

		dma_fence_wait(in_fence, true);
		printk("in-fence signaled, process exit\n");

		dma_fence_put(in_fence);

		break;

	case DMA_FENCE_EXPORT_CMD:
		if (!out_fence)
			return -EINVAL;

		sync_file = sync_file_create(out_fence);
		out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
		fd_install(out_fence_fd, sync_file->file);

		set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &out_fence->flags);

		if (copy_to_user((void __user *)arg, &out_fence_fd, sizeof(int)) != 0)
			return -EFAULT;

		printk("Created an out-fence fd = %d, out_fence = 0x%px,seqno %lld.\n", 
				out_fence_fd, out_fence, out_fence->seqno);

		dma_fence_put(out_fence);
		break;

	default:
		printk("bad cmd.\n");
		break;
	}

	return 0;
}

static struct dma_fence *create_fence(void)
{
	struct dma_fence *fence;

	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
	if (!fence)
		return NULL;

	dma_fence_init(fence, &fence_ops, &fence_lock, 0, 90);
	dma_fence_get(fence);

	return fence;
}

static int fence_open(struct inode *inode, struct file *filp)
{
	struct dma_fence *out_fence;

	/* create an new fence */
	out_fence = create_fence();
	if (!out_fence)
		return -ENOMEM;

	filp->private_data = out_fence;
	init_waitqueue_head(&poll_wait_head);

	return 0;
}

static int fence_close(struct inode *inode, struct file *filp)
{
	struct dma_fence *out_fence = NULL;

	out_fence = (struct dma_fence *)filp->private_data;
	if (out_fence == NULL) {
		pr_err("%s line %d.fatal error. fence is null.\n", __func__, __LINE__);
		return -1;
	}

	dma_fence_put(out_fence);
	return 0;
}

static __poll_t fence_poll(struct file *filp, struct poll_table_struct *wait)
{
	__poll_t mask = 0;

	poll_wait(filp, &poll_wait_head, wait);
	if (poll_signaled) {
		mask = EPOLLIN | EPOLLRDNORM;
		poll_signaled = 0;
		printk("%s line %d, poll signaled.\n", __func__, __LINE__);
	}

	return mask;
}

static struct file_operations fence_fops = {
	.owner  = THIS_MODULE,
	.unlocked_ioctl = fence_ioctl,
	.open = fence_open,
	.poll = fence_poll,
	.release = fence_close,
};

static struct miscdevice mdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "dma-fence",
	.fops = &fence_fops,
};

static int __init dma_fence_demo_init(void)
{
	return misc_register(&mdev);
}

static void __exit dma_fence_demo_unint(void)
{
	misc_deregister(&mdev);
}

module_init(dma_fence_demo_init);
module_exit(dma_fence_demo_unint);

MODULE_AUTHOR("czl");
MODULE_LICENSE("GPL v2");

测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <poll.h>

#define DMA_FENCE_WAIT_CMD              _IOWR('f', 0, int)
#define DMA_FENCE_EXPORT_CMD            _IOWR('f', 1, int)
#define DMA_FENCE_SIGNAL_CMD            _IO('f', 2)
#define DEFAULT_POLLMASK                (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)

//#define BLOCKING_IN_KERNEL

static int fd = -1;

static inline int sync_wait(int fd, int timeout)
{
	struct pollfd fds = {0};
	int ret;

	fds.fd = fd;
	fds.events = POLLIN;

	do {
		ret = poll(&fds, 1, timeout);
		if (ret > 0) {
			if (fds.revents & (POLLERR | POLLNVAL)) {
				errno = EINVAL;
				return -1;
			}

			printf("%s line %d, DEFAULT_POLLMASK = 0x%x, fds_revents = 0x%x.\n", \
			       __func__, __LINE__, DEFAULT_POLLMASK, fds.revents);
			return 0;
		} else if (ret == 0) {
			errno = ETIME;
			return -1;
		}
	} while (ret == -1 && (errno == EINTR || errno == EAGAIN));

	return ret;
}

static void *signal_pthread(void *arg)
{
	sleep(10);

	if (ioctl(fd, DMA_FENCE_SIGNAL_CMD) < 0) {
		perror("get out fence fd fail\n");
	}

	return NULL;
}

int main(void)
{
	int out_fence_fd;
	pthread_t tidp;

	fd = open("/dev/dma-fence", O_RDWR | O_NONBLOCK, 0);
	if (-1 == fd) {
		printf("Cannot open dma-fence dev\n");
		exit(1);
	}

	if (ioctl(fd, DMA_FENCE_EXPORT_CMD, &out_fence_fd) < 0) {
		perror("get out fence fd fail\n");
		close(fd);
		return -1;
	}

	printf("Get an out-fence fd = %d\n", out_fence_fd);

	if ((pthread_create(&tidp, NULL, signal_pthread, NULL)) == -1) {
		printf("create error!\n");
		close(out_fence_fd);
		close(fd);
		return -1;
	}

#ifdef BLOCKING_IN_KERNEL
	printf("waiting out-fence to be signaled on kernel side ...\n");
	if (ioctl(fd, DMA_FENCE_WAIT_CMD, &out_fence_fd) < 0) {
		perror("get out fence fd fail\n");
		close(out_fence_fd);
		close(fd);
		return -1;
	}
#else
	printf("Waiting out-fence to be signaled on USER side ...\n");
	sync_wait(out_fence_fd, -1);
#endif

	printf("out-fence is signaled\n");
	if (pthread_join(tidp, NULL)) {
		printf("thread is not exit...\n");
		return -1;
	}

	close(out_fence_fd);
	close(fd);

	return 0;
}

测试过程,安装内核模块后,运行用例,程序运行卡10秒钟后,signal线程发出信号,主线程等到信号后退出。

fence中挂接了多个callback.

同步方式

通过signal/wait进行同步,wait阻塞线程,阻塞的线程通过signal触发唤醒。

另一种方式使用singal/poll机制,内核驱动通过调用sync_file接口将fence包装成一个sync file对象并和一个匿名文件建立关联,将匿名文件FD导出到其它模块,其它模块通过FD得到匿名文件,进而调用poll进入阻塞状态,直到fence_signal将其唤醒。

匿名文件fops定义如下。

其POLL函数会注册一个FENCE唤醒的CALLBACK 回调函数,然后将当前线程加入等待队列头,此回调函数在FENCE SIGNAL被触发时调用,唤醒在sync_file队列头上睡眠的线程。

一般以上两种同步情况一方会调用dma_fence_signal,但是另一方不必调用poll或者dma_fence_wait,这种情况下实际上只有外部定义的CALLBACK会被调用到,内部的wake up callback没有被注册,自然也不会被调用。

sync_file

内核中可以通过sync_file_create->sync_file_alloc 生成了一个anon_inode_inode对应的匿名文件,之后,用户驱动就可以从当前进程的文件表中获取一个未占用的FD,将后者同FILE绑定起来,之后将FD返回给用户态,之后,用户态就可以通过标准的文件系统调用操作FENCE了。典型的比如FENCE 对POLL 调用的支持。

关于匿名文件的创建流程,可以参考内核源码中KVM虚拟机匿名文件的创建过程。

sync_file poll的流程如下:

安卓中sync_wait fence阻塞原理,这里的FD对应的就是内核中的sync_file.

https://android.googlesource.com/platform/system/core/+/master/libsync/sync.c

等待队列的结构:

动态FENCE同步:

动态创建FENCE并同步,注意其中 KREF引用计数的用法,通过饮用计数控制FENCE的生命期,FENCE对象可以独立于某个驱动模块存在。

#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include <asm/ioctl.h>
#include <asm/fcntl.h>
#include <linux/uaccess.h>
#include <linux/dma-fence.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/sync_file.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/syscalls.h>
#include <linux/fdtable.h>
#include <linux/version.h>

#define DMA_FENCE_WAIT_CMD              _IOWR('f', 0, int)
#define DMA_FENCE_EXPORT_CMD            _IOWR('f', 1, int)
#define DMA_FENCE_SIGNAL_CMD            _IO('f', 2)

static int in_fence_fd = -1;
static int out_fence_fd = -1;
static int poll_signaled = 0;

static struct dma_fence_cb cb;
static wait_queue_head_t poll_wait_head;

static DEFINE_SPINLOCK(fence_lock);

static __maybe_unused struct sync_file *sync_file_fdget(int fd)
{
	struct file *file = fget(fd);
	if (file == NULL) {
		return NULL;
	}

	return file->private_data;
}

static void dma_fence_cb(struct dma_fence *f, struct dma_fence_cb *cb)
{
	//dump_stack();
	printk("dma-fence callback!, seqno %lld.\n", f->seqno);
}

static const char *dma_fence_get_name(struct dma_fence *fence)
{
	return "dma-fence-example";
}

static const struct dma_fence_ops fence_ops = {
	.get_driver_name = dma_fence_get_name,
	.get_timeline_name = dma_fence_get_name,
};

static void iter_fence_callbac(struct dma_fence *fence)
{
	unsigned long flags;
	struct dma_fence_cb *cur, *tmp;

	spin_lock_irqsave(fence->lock, flags);
	list_for_each_entry_safe(cur, tmp, &fence->cb_list, node) {
		printk("%s line %d cur->func = 0x%px, 0x%pS.\n", __func__, __LINE__, cur->func, cur->func);
	}
	spin_unlock_irqrestore(fence->lock, flags);

	return;
}

static long fence_ioctl(struct file *filp,
                        unsigned int cmd, unsigned long arg)
{
	struct sync_file *sync_file;
	struct dma_fence *in_fence;
	struct dma_fence *fence;
	int signal_fd;
	static unsigned long seqno = 0;

	switch (cmd) {
	case DMA_FENCE_SIGNAL_CMD:
		if (copy_from_user(&signal_fd, (void __user *)arg, sizeof(int)) != 0)
			return -EFAULT;

		in_fence = sync_file_get_fence(signal_fd);
		if (!in_fence)
			return -EINVAL;

		iter_fence_callbac(in_fence);

		printk("signal fence, seqno %lld.\n", in_fence->seqno);
		dma_fence_signal(in_fence);
		dma_fence_put(in_fence);

		//wake_up_interruptible(&poll_wait_head);
		//poll_signaled = 1;
		break;
	case DMA_FENCE_WAIT_CMD:
		if (copy_from_user(&in_fence_fd, (void __user *)arg, sizeof(int)) != 0)
			return -EFAULT;

		// will increase the fence kref refcount.
		in_fence = sync_file_get_fence(in_fence_fd);
		if (!in_fence)
			return -EINVAL;

		printk("get in-fence from fd = %d, in_fence 0x%px.\n", in_fence_fd, in_fence);

		// must be held before goto wait for loop scenario.
		clear_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &in_fence->flags);

		//INIT_LIST_HEAD(&in_fence->cb_list);

		if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &in_fence->flags)) {
			printk("%s line %d, fence has been signaled.\n", __func__, __LINE__);
		} else {
			printk("%s line %d, fence has not been signaled.\n", __func__, __LINE__);
		}

		/* add a callback func */
		dma_fence_add_callback(in_fence, &cb, dma_fence_cb);

		printk("waiting in-fence seqno %lld to be signaled, process is blocking ...\n",
		       in_fence->seqno);

		dma_fence_wait(in_fence, true);
		printk("in-fence signo %lld signaled, fence refcount %d.\n", in_fence->seqno, kref_read(&in_fence->refcount));

		// now relese the fence corrspont sync file get.
		dma_fence_put(in_fence);

		//release the first create refcount.
		dma_fence_put(in_fence);

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
		__close_fd(current->files, in_fence_fd);
#else
		close_fd(in_fence_fd);
#endif
		printk("in-fence signo %lld signaled, fence refcount %d.\n", in_fence->seqno, kref_read(&in_fence->refcount));
		break;
	case DMA_FENCE_EXPORT_CMD:
		fence = kzalloc(sizeof(*fence), GFP_KERNEL);
		if (!fence)
			return -1;

		// dma_fence_init will call kref_init set the fence krefcount to 1.
		dma_fence_init(fence, &fence_ops, &fence_lock, 0, seqno ++);

		// get the fence ,now refcount to 2.
		dma_fence_get(fence);

		// get the fence ,now refcount to 3.
		sync_file = sync_file_create(fence);
		out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
		fd_install(out_fence_fd, sync_file->file);

		set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &fence->flags);

		if (copy_to_user((void __user *)arg, &out_fence_fd, sizeof(int)) != 0)
			return -EFAULT;

		printk("Created an out-fence fd = %d, fence = 0x%px,seqno %lld, outfd %d.\n",
		       out_fence_fd, fence, fence->seqno, out_fence_fd);


		// put the fence corrspontd sync_file_get, now refcount to 2.
		dma_fence_put(fence);

		// put the fence, now refcount to 1, now we use it.
		dma_fence_put(fence);
		printk("out-fence signo %lld signaled, fence refcount %d.\n", fence->seqno, kref_read(&fence->refcount));

		break;

	default:
		printk("bad cmd.\n");
		break;
	}

	return 0;
}

static __maybe_unused struct dma_fence *create_fence(void)
{
	struct dma_fence *fence;

	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
	if (!fence)
		return NULL;

	dma_fence_init(fence, &fence_ops, &fence_lock, 0, 90);
	dma_fence_get(fence);

	return fence;
}

static int fence_open(struct inode *inode, struct file *filp)
{
#if 0
	struct dma_fence *out_fence;

	/* create an new fence */
	out_fence = create_fence();
	if (!out_fence)
		return -ENOMEM;

	filp->private_data = out_fence;
	init_waitqueue_head(&poll_wait_head);
#endif

	return 0;
}

static int fence_close(struct inode *inode, struct file *filp)
{
#if 0
	struct dma_fence *out_fence = NULL;

	out_fence = (struct dma_fence *)filp->private_data;
	if (out_fence == NULL) {
		pr_err("%s line %d.fatal error. fence is null.\n", __func__, __LINE__);
		return -1;
	}

	dma_fence_put(out_fence);
#endif
	return 0;
}

static __poll_t fence_poll(struct file *filp, struct poll_table_struct *wait)
{
	__poll_t mask = 0;

	poll_wait(filp, &poll_wait_head, wait);
	if (poll_signaled) {
		mask = EPOLLIN | EPOLLRDNORM;
		poll_signaled = 0;
		printk("%s line %d, poll signaled.\n", __func__, __LINE__);
	}

	return mask;
}

static struct file_operations fence_fops = {
	.owner  = THIS_MODULE,
	.unlocked_ioctl = fence_ioctl,
	.open = fence_open,
	.poll = fence_poll,
	.release = fence_close,
};

static struct miscdevice mdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "dma-fence",
	.fops = &fence_fops,
};

static int __init dma_fence_demo_init(void)
{
	return misc_register(&mdev);
}

static void __exit dma_fence_demo_unint(void)
{
	misc_deregister(&mdev);
}

module_init(dma_fence_demo_init);
module_exit(dma_fence_demo_unint);

MODULE_AUTHOR("czl");
MODULE_LICENSE("GPL v2");

测试用例

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <poll.h>

#define DMA_FENCE_WAIT_CMD              _IOWR('f', 0, int)
#define DMA_FENCE_EXPORT_CMD            _IOWR('f', 1, int)
#define DMA_FENCE_SIGNAL_CMD            _IO('f', 2)
#define DEFAULT_POLLMASK                (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)

#define BLOCKING_IN_KERNEL

static int fd = -1;

static inline int sync_wait(int fd, int timeout)
{
	struct pollfd fds = {0};
	int ret;

	fds.fd = fd;
	fds.events = POLLIN;

	do {
		ret = poll(&fds, 1, timeout);
		if (ret > 0) {
			if (fds.revents & (POLLERR | POLLNVAL)) {
				errno = EINVAL;
				return -1;
			}

			printf("%s line %d, DEFAULT_POLLMASK = 0x%x, fds_revents = 0x%x.\n", \
			       __func__, __LINE__, DEFAULT_POLLMASK, fds.revents);
			return 0;
		} else if (ret == 0) {
			errno = ETIME;
			return -1;
		}
	} while (ret == -1 && (errno == EINTR || errno == EAGAIN));

	return ret;
}

static void *signal_pthread(void *arg)
{
	while (1) {
		sleep(1);
		if (ioctl(fd, DMA_FENCE_SIGNAL_CMD, arg) < 0) {
			perror("get out fence fd fail\n");
		}
	}
	return NULL;
}

int main(void)
{
	int out_fence_fd;
	pthread_t tidp;

	fd = open("/dev/dma-fence", O_RDWR | O_NONBLOCK, 0);
	if (-1 == fd) {
		printf("Cannot open dma-fence dev\n");
		exit(1);
	}

	if (ioctl(fd, DMA_FENCE_EXPORT_CMD, &out_fence_fd) < 0) {
		perror("get out fence fd fail\n");
		close(fd);
		return -1;
	}

	printf("Get an out-fence fd = %d\n", out_fence_fd);

	if ((pthread_create(&tidp, NULL, signal_pthread, &out_fence_fd)) == -1) {
		printf("create error!\n");
		close(out_fence_fd);
		close(fd);
		return -1;
	}

#ifdef BLOCKING_IN_KERNEL
	while (1) {
		printf("waiting out-fence to be signaled on kernel side ...\n");
		if (ioctl(fd, DMA_FENCE_WAIT_CMD, &out_fence_fd) < 0) {
			perror("get out fence fd fail\n");
			close(out_fence_fd);
			close(fd);
			return -1;
		}

		if (ioctl(fd, DMA_FENCE_EXPORT_CMD, &out_fence_fd) < 0) {
			perror("get out fence fd fail\n");
			close(fd);
			return -1;
		}
	}
#else
	printf("Waiting out-fence to be signaled on USER side ...\n");
	sync_wait(out_fence_fd, -1);
#endif

	printf("out-fence is signaled\n");
	if (pthread_join(tidp, NULL)) {
		printf("thread is not exit...\n");
		return -1;
	}

	close(out_fence_fd);
	close(fd);

	return 0;
}

在DRM GPU调度器中的使用模拟

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched/signal.h>
#include <linux/dma-fence.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#define assert(expr)   \
        if (!(expr)) { \
                printk( "Assertion failed! %s,%s,%s,line=%d\n",\
                                #expr,__FILE__,__func__,__LINE__); \
                BUG(); \
        }

#define num_hw_submission 128
struct fence_driver {
	uint64_t                        gpu_addr;
	volatile uint32_t               *cpu_addr;
	uint32_t                        sync_seq;
	atomic_t                        last_seq;
	bool                            initialized;
	unsigned                        num_fences_mask;
	spinlock_t                      lock;
	struct dma_fence                **fences;
	struct mutex                    mutex;
	struct dma_fence                *last_fence;
	struct timer_list               timer;
};

static struct fence_driver *ring;
static uint32_t fence_seq;
static struct task_struct *fence_emit_task;
static struct task_struct *fence_recv_task;
static struct kmem_cache *fence_slab;
static const char *dma_fence_get_name(struct dma_fence *fence)
{
	return "dma-fence-drv";
}

static const struct dma_fence_ops fence_ops = {
	.get_driver_name = dma_fence_get_name,
	.get_timeline_name = dma_fence_get_name,
};

static int fence_recv_task_thread(void *data)
{
	uint32_t seqno_next = 0;

	//mutex_lock(&ring->mutex);
	while (ring->initialized == false) {
		set_current_state(TASK_UNINTERRUPTIBLE);
		if (ring->initialized == true) {
			break;
		}
		//mutex_unlock(&ring->mutex);
		schedule();
		//mutex_lock(&ring->mutex);
	}

	set_current_state(TASK_RUNNING);
	//mutex_unlock(&ring->mutex);

	while (!kthread_should_stop()) {
#if 0
		msleep(1000);
		printk("%s line %d.\n", __func__, __LINE__);
#else
		uint32_t seq, last_seq;
		int r;

		do {
			last_seq = atomic_read(&ring->last_seq);
			seq = *ring->cpu_addr;

			if (kthread_should_stop())
				return 0;
		} while (atomic_cmpxchg(&ring->last_seq, last_seq, seq) != last_seq);

		//printk("%s line %d, last_seq %d, seq %d, sync_seq %d.\n", __func__, __LINE__, last_seq, seq, ring->sync_seq);

		if (unlikely(seq == last_seq)) {
			msleep(10);
			continue;
		}

		assert(seq > last_seq);

		last_seq &= ring->num_fences_mask;
		seq &= ring->num_fences_mask;

		//printk("%s line %d, last_seq %d, seq %d, sync_seq %d.\n", __func__, __LINE__, last_seq, seq, ring->sync_seq);
		do {
			struct dma_fence *fence, **ptr;

			++last_seq;
			last_seq &= ring->num_fences_mask;
			ptr = &ring->fences[last_seq];
			fence = rcu_dereference_protected(*ptr, 1);

			RCU_INIT_POINTER(*ptr, NULL);
			if (!fence) {
				continue;
			}

			if (seqno_next == 0 || seqno_next == fence->seqno) {
				seqno_next = fence->seqno + 1;
			} else { /*if (seqno_next != 0 && seqno_next != fence->seqno)*/
				pr_err("%s line %d, seqno is not continue, exptect %d, actual %lld.\n",
				       __func__, __LINE__, seqno_next, fence->seqno);
			}

#if 1
			printk("%s line %d, last_seq %d, seq %d, signal %lld, slot %d.\n",
			       __func__, __LINE__, last_seq, seq, fence->seqno, last_seq);
#endif

			r = dma_fence_signal(fence);

			if (kthread_should_stop())
				return 0;

			if (r) {
				BUG();
			}

			dma_fence_put(fence);
		} while (last_seq != seq);

		//msleep(1000);
#endif
	}

	set_current_state(TASK_RUNNING);
	return 0;
}

static int fence_emit_task_thread(void *data)
{
	int r;

	//mutex_lock(&ring->mutex);
	while (ring->initialized == false) {
		set_current_state(TASK_UNINTERRUPTIBLE);
		if (ring->initialized == true) {
			break;
		}

		//mutex_unlock(&ring->mutex);
		schedule();
		//mutex_lock(&ring->mutex);
	}

	set_current_state(TASK_RUNNING);
	//mutex_unlock(&ring->mutex);

	while (!kthread_should_stop()) {
#if 0
		msleep(1000);
		printk("%s line %d.\n", __func__, __LINE__);
#else
		struct dma_fence __rcu **ptr;
		struct dma_fence *fence;
		uint32_t seq;

		fence = kmem_cache_alloc(fence_slab, GFP_KERNEL);
		if (fence == NULL) {
			pr_err("%s line %d, alloc fence from fence slab failure.\n",
			       __func__, __LINE__);
			return -1;
		}

		seq = ++ring->sync_seq;
		dma_fence_init(fence, &fence_ops, &ring->lock, 0, seq);

		ptr = &ring->fences[seq & ring->num_fences_mask];

		//printk("%s line %d, seq = %d.\n", __func__, __LINE__, seq);

		if (unlikely(rcu_dereference_protected(*ptr, 1))) {
			struct dma_fence *old;
			int diff;

			rcu_read_lock();
			old = dma_fence_get_rcu_safe(ptr);
			rcu_read_unlock();

			if (old) {
				dma_fence_get(old);
				ring->last_fence = old;
				r = dma_fence_wait(old, false);
				ring->last_fence = NULL;
				dma_fence_put(old);

				if (kthread_should_stop())
					continue;

				if (r)
					return r;

				diff = seq - old->seqno;
				printk("%s line %d, fence wokenup, seqno %lld, seq %d, slot %d, diff %d.\n",
				       __func__, __LINE__, old->seqno, seq, seq & ring->num_fences_mask, diff);
				if (diff != num_hw_submission * 2) {
					pr_err("%s line %d, fatal error, diff not match totoal ring.\n",
					       __func__, __LINE__);
				}
			}
		}

		/*
		 *printk("%s line %d, fence add, seqno %lld, seq %d, slot %d.\n",
		 *                __func__, __LINE__, fence->seqno, seq, seq & ring->num_fences_mask);
		 */
		//printk("%s line %d, fence emit.\n", __func__, __LINE__);
		rcu_assign_pointer(*ptr, dma_fence_get(fence));
#endif
	}

	set_current_state(TASK_RUNNING);
	return 0;
}


void gpu_job_consume_interrupt(struct timer_list *timer)
{

	uint32_t seq, oldseq;

	seq = ring->sync_seq;
	oldseq = fence_seq;

	// trigger a job done on device.
	if (fence_seq == 0) {
		if (seq > 6)
			fence_seq = seq - 4;
	} else if ((seq - fence_seq) > 100) {
		fence_seq += (seq - fence_seq) / 2;
		assert(fence_seq > oldseq);
	}

	printk("%s line %d, timer trigger job.\n", __func__, __LINE__);
	mod_timer(timer, jiffies + HZ / 2);
}

static int __init fencedrv_init(void)
{
	if ((num_hw_submission & (num_hw_submission - 1)) != 0) {
		pr_err("%s line %d, num_hw_submission must be power of two.\n",
		       __func__, __LINE__);
		return -1;
	}

	ring = kzalloc(sizeof(*ring), GFP_KERNEL);
	if (ring == NULL) {
		pr_err("%s line %d, alloc fence driver failure.\n",
		       __func__, __LINE__);
		return -ENOMEM;
	}

	ring->cpu_addr = &fence_seq;
	ring->gpu_addr = (uint64_t)&fence_seq;
	ring->sync_seq = 0;
	atomic_set(&ring->last_seq, 0);
	ring->initialized = false;
	ring->last_fence = NULL;
	ring->num_fences_mask = num_hw_submission * 2 - 1;

	spin_lock_init(&ring->lock);
	ring->fences = kcalloc(num_hw_submission * 2, sizeof(void *), GFP_KERNEL);
	if (!ring->fences) {
		pr_err("%s line %d, alloc fence buffer failure.\n",
		       __func__, __LINE__);
		return -ENOMEM;
	}

	fence_slab = kmem_cache_create("fence_slab", sizeof(struct dma_fence), 0,
	                               SLAB_HWCACHE_ALIGN, NULL);
	if (!fence_slab) {
		pr_err("%s line %d, alloc fence_slab falure.\n",
		       __func__, __LINE__);
		return -ENOMEM;
	}

	mutex_init(&ring->mutex);

	fence_emit_task = kthread_run(fence_emit_task_thread, NULL, "fence_emit");
	if (IS_ERR(fence_emit_task)) {
		pr_err("%s line %d, create fence emit tsk failure.\n",
		       __func__, __LINE__);
		return -1;
	}

	fence_recv_task = kthread_run(fence_recv_task_thread, NULL, "fence_recv");
	if (IS_ERR(fence_recv_task)) {
		pr_err("%s line %d, create fence recv tsk failure.\n",
		       __func__, __LINE__);
		return -1;
	}

	timer_setup(&ring->timer, gpu_job_consume_interrupt, TIMER_IRQSAFE);
	add_timer(&ring->timer);
	mod_timer(&ring->timer, jiffies + HZ / 2);

	printk("%s line %d, module init.\n", __func__, __LINE__);

	ring->initialized = true;
	wake_up_process(fence_emit_task);
	wake_up_process(fence_recv_task);

	return 0;
}

static void __exit fencedrv_exit(void)
{
	printk("%s line %d, module unload task begin.\n", __func__, __LINE__);

	if ((ring->last_fence != NULL) &&
	    (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &ring->last_fence->flags))) {
		dma_fence_signal(ring->last_fence);
		dma_fence_put(ring->last_fence);
	}

	del_timer(&ring->timer);
	kthread_stop(fence_emit_task);
	printk("%s line %d, module unload task mid.\n", __func__, __LINE__);
	kthread_stop(fence_recv_task);
	printk("%s line %d, module unload task end.\n", __func__, __LINE__);

	rcu_barrier();
	kmem_cache_destroy(fence_slab);
	kfree(ring->fences);
	kfree(ring);

	printk("%s line %d, module unload.\n", __func__, __LINE__);
}

module_init(fencedrv_init);
module_exit(fencedrv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("czl");

makefile

ifneq ($(KERNELRELEASE),)
CFLAGS_seqfile.o:=-I$(src)
obj-m:=fencedrv.o
else
KERNELDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
 
clean:
	rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.mod .*.cmd *.order
format:
	astyle --options=linux.astyle *.[ch]
endif

FENCE的历史演进

在4.9版附近,进行了一次API重命名,从fence_xxxx变为现在我们看到的样子:

首次提交发生在v3.16,大概是2014年中

变更点提交分别为:

分析

深入分析——Linux DMA Fence_Bystander_J的博客-CSDN博客

fence生命期

dma_fence的生命期由dma_fence_init/dma_fence_get/dma_fence_put三个接口控制kref refcount实现:

dma_fence_init将refcount置1。

dma_fence_put递减引用计数,如果引用计数归0,则调用release callback释放fence占用的内存。

dma_fence_get增加引用计数。

由于fence初始化时必须调用dma_fence_init首先将引用计数置为1,所以如果一个fence在使用过程中没有调用dma_fence_get, 则只需要调用一次dma_fence_put,即可将fence释放。

参考资料

Android 重学系列 fence原理 - 简书

https://www.cnblogs.com/yaongtime/p/14305463.html

AMD GPU任务调度(3) —— fence机制_享乐主的博客-CSDN博客


结束

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值