首页 > 代码库 > uio驱动中multi-instance的问题分析
uio驱动中multi-instance的问题分析
首先我们来看问题的背景,上层的encoder/decoder的工作流程是这样的:
Work procedure1. Open the uio0 device to get the fd2. Get the VPU register base address, work buffer and SRAM buffer address, and mmap them to user space in order to access them in user space directly3. Clock on4. Catch a lock5. Issue command via writing VPU register to let VPU working6. Wait interrupt via poll system call, there is a interrupt when the return value of poll > 0, it means one frame decode/encode done7. Clock off8. Unlock9. Repeat from #310. Close fd
从上述流程中可以看到,用户态程序通过写寄存器让vpu硬件开始工作,然后就调用poll阻塞,直到有中断产生(通常表示一帧处理结束),重新唤醒这个进程,poll调用返回,我们来看这个poll机制在driver中是如何实现的。
APP调用poll,陷入内核sys_poll函数,它接着调用do_sys_poll,这个函数也在fs/select.c中,我们忽略其他代码,
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout) { …… poll_initwait(&table); …… fdcount = do_poll(nfds, head, &table, timeout); …… }
poll_initwait函数非常简单,它初始化了一个poll_wqueues变量,其中最重要的是init_poll_funcptr(&pwq->pt, __pollwait);这里__pollwait是一个回调函数,将来会在驱动自己实现的poll中调用到,稍后用到了再解释这个函数的作用。
再看主要部分do_poll
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, struct timespec *end_time) { poll_table* pt = &wait->pt; ktime_t expire, *to = NULL; int timed_out = 0, count = 0; unsigned long slack = 0; /* Optimise the no-wait case */ if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { pt = NULL; timed_out = 1; } if (end_time && !timed_out) slack = estimate_accuracy(end_time); for (;;) { struct poll_list *walk; for (walk = list; walk != NULL; walk = walk->next) { struct pollfd * pfd, * pfd_end; pfd = walk->entries; pfd_end = pfd + walk->len; for (; pfd != pfd_end; pfd++) { /* * Fish for events. If we found one, record it * and kill the poll_table, so we don‘t * needlessly register any other waiters after * this. They‘ll get immediately deregistered * when we break out and return. */ if (do_pollfd(pfd, pt)) { count++; pt = NULL; } } } /* * All waiters have already been registered, so don‘t provide * a poll_table to them on the next loop iteration. */ pt = NULL; if (!count) { count = wait->error; if (signal_pending(current)) count = -EINTR; } if (count || timed_out) break; /* * If this is the first loop and we have a timeout * given, then we convert to ktime_t and set the to * pointer to the expiry value. */ if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) timed_out = 1; } return count; }
do_pollfd函数最终调用了驱动的poll函数,可以看到,如果APP第一次poll调用进来,所有的do_pollfd都返回0,那么函数最终会调用到poll_schedule_timeout进行睡眠
static unsigned int uio_poll(struct file *filep, poll_table *wait){ struct uio_listener *listener = filep->private_data; struct uio_device *idev = listener->dev; s32 event_count; if (!idev->info->irq) return -EIO; poll_wait(filep, &idev->wait, wait); event_count = atomic_read(&idev->event); if (listener->event_count != event_count) { listener->event_count = event_count; return POLLIN | POLLRDNORM; } return 0;}
uio_poll首先调用了poll_wait函数,不要被这个函数的名字给误导,它本身并不会阻塞,它其实只是调用了之前提到的回调函数__pollwait
,而__pollwait的主要作用就是注册了一个唤醒函数pollwake,只有在listener->event_count != event_count的时候,才会返回POLLIN|POLLRDNORM,而这个条件如何达成呢,答案是中断,uio驱动通用的中断处理函数如下
static irqreturn_t uio_interrupt(int irq, void *dev_id){ struct uio_device *idev = (struct uio_device *)dev_id; irqreturn_t ret = idev->info->handler(irq, idev->info); if (ret == IRQ_HANDLED) uio_event_notify(idev->info); return ret;}
只要具体设备驱动(比如VPU)的中断处理程序(vpu_func_irq_handler,这个函数很简单,就是读一个中断状态寄存器,只要标志中断的那一位为1,就做些简单处理,然后返回IRQ_HANDLED,所以这里有风险,就是有时候不仅仅一帧处理完毕会产生中断,一个stream/bus error也会产生中断)返回IRQ_HANDLED,它就会去调用uio_event_notify函数,这个函数如下
void uio_event_notify(struct uio_info *info){ struct uio_device *idev = info->uio_dev; atomic_inc(&idev->event); wake_up_interruptible(&idev->wait); kill_fasync(&idev->async_queue, SIGIO, POLL_IN);}
这个函数会将设备的event进行inc操作,然后调用wake_up_interruptible,它最终会调用之前注册的pollwake函数,使得之前阻塞的poll_schedule_timeout函数执行执行,从而进行第一次循环,do_pollfd再一次被调到,这一次由于idev->event已经加1,返回就不再是0,这样会使得do_poll成功返回。
下面分析multi-instances会出现的问题,因为每个instance都对应一个fd(一个fd对应一个listener ,listener的event_count都初始化为0),这样当instance A成功进行了一次poll操作之后,listener A的event count为1,idev->event也为1(它是全局的),这样当instance B调用poll的时候,第一次调用到uio_poll的时候就会立马返回(因为满足listener->event_count != event_count),poll也会随之立刻成功返回,上层APP会误以为一次中断到来,就会出错。
解决的方法有3个:
1. 因为每个instance在工作之前都要获得lock,因为底层的硬件同时只能由一个instance独占,在获得lock(通过ioctl)的这个时间节点,做一次event count的sync操作,也就是使得instance对应的listener->event_count等于idev->event,这样之后的工作流程就不会有问题
因为这个sync操作得在uio_hantro.c中操作,而且需要操作idev,所以需要将uio_device的定义(原先在uio.c中)挪到头文件当中.
2. 在
if (listener->event_count != event_count) { listener->event_count = event_count; uio_event_sync(); return POLLIN | POLLRDNORM;}
在上面的位置插入uio_event_sync函数,将所有其他instance的listener->event_count都赋值为idev->event
3. 在userspace处理,每次poll返回之后check一下中断状态寄存器,看是否是fake的中断,如果是则重新调用poll,直到真的有中断产生
uio驱动中multi-instance的问题分析
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。