uio驱动中multi-instance的问题分析

首先我们来看问题的背景,上层的encoder/decoder的工作流程是这样的:

Work procedure

1. Open the uio0 device to get the fd
2. 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 directly
3. Clock on
4. Catch a lock
5. Issue command via writing VPU register to let VPU working
6. Wait interrupt via poll system call, there is a interrupt when the return value of poll > 0, it means one frame decode/encode done
7. Clock off
8. Unlock
9. Repeat from #3
10. 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,直到真的有中断产生

时间: 2024-08-29 06:47:26

uio驱动中multi-instance的问题分析的相关文章

Linux设备驱动中的阻塞和非阻塞I/O

[基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足. 2.非阻塞 非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止. 当用户空间的应用程序调用read(),write()等方法时,若设备的资源不能被获取,而用户又希望以阻塞的方式来访问设备,驱动程序应当在设备驱动层的

C++11中once_flag,call_once实现分析

本文的分析基于llvm的libc++,而不是gun的libstdc++,因为libstdc++的代码里太多宏了,看起来蛋疼. 在多线程编程中,有一个常见的情景是某个任务只需要执行一次.在C++11中提供了很方便的辅助类once_flag,call_once. 声明 首先来看一下once_flag和call_once的声明: struct once_flag { constexpr once_flag() noexcept; once_flag(const once_flag&) = delete

字符设备驱动之从用户程序中的系统调用到驱动中的具体实现

引:我们知道每一个字符设备在内核中都有一个cdev结构来描述之,而这个结构比较重要的一个成员就是 const struct file_operations *ops; 该结构的作用是将用户程序中的系统调用和驱动程序中的具体实现函数一一对应起来.当在用户程序中对一个字符设备文件调用某一系统调用时,就知道该对这个字符设备调用哪个具体的函数,但是问题来了,下面看两个函数原型: //这是read系统调用的原型 ssize_t read(int fd, void *buf, size_t count);

20150518 Linux设备驱动中的并发控制

20150518 Linux设备驱动中的并发控制 2015-05-18 Lover雪儿 总结一下并发控制的相关知识: 本文参考:华清远见<Linux 设备驱动开发详解>—第7章 Linux 设备驱动中的并发控制,更多详细内容请看原书 一.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量.静态变量等)的访问则很容易导致竞态(race conditions). 在 Linux 内核中,主要的竞态发生于如下几种情况:

驱动中PAGED_CODE的作用

参考:http://blog.csdn.net/broadview2006/article/details/4171397 里面的内容出自<Windows内核情景分析> 简而言之,Windows并没有将运行在Ring 0的代码全部视为内核,而是区分为Kernel和Executive,Executive可以理解为"管理层"的意思,解释为"执行体"不合理. 其中,Kernel是狭义的内核,里面的代码包括用到的数据,都是常驻在物理内存中的,不支持分页机制. 而

[C#.NET] C#中使用反射的性能分析

最近在研究一个可配置系统的框架,在代码中大量使用了反射的方法,虽然借鉴到其他的语言,如java中反射性能都比较差,但是想到c#既然是一种强类型的语言,对于AppDomain中的类的调用应该性能不会差很多.   今天在mvp站点上看到有人说反射的性能很差,要避免使用,就写了一个简单的例子测试了一下   测试类如下:   namespace ReflectionTest.Test   {    public class CTester    {    public CTester()    {   

让MT7620完美支持32M SPI Flash(W25Q256) — 兼谈设备驱动中的shutdown方法

前言 OpenWrt的最新kernel(3.14.28)已经能够支持32M SPI Flash的读写以及擦除操作.然而,可能是系统考虑不周,亦或是MT7620系统的BUG,在配置了W25Q256的MT7620开发板系统上,无法soft reset!经过查阅相关资料,发现,MT7620默认支持24bit(3byte)的spi地址模式,而要支持32M以上的spi flash,则必须切换到32bit(4byte)地址模式.在soft reset的时候,spi停留在了32bit模式,没有切换回默认的24

Linux设备驱动中的IO模型---阻塞和非阻塞IO【转】

在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程——网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用. 回顾一下在Unix/Linux下共有五种I/O模型,分别是: a -- 阻塞I/Ob -- 非阻塞I/Oc -- I/O复用(select和poll)d -- 信号驱动I/O(SIGIO)e -- 异步I/O(Posix.1的aio_系列函数) 下面我们先学习阻塞I/O.非阻塞I/O .I/O复用(select和poll),先学习一下基础概念 a -- 阻塞 

使用python中的matplotlib进行绘图分析数据

http://blog.csdn.net/pipisorry/article/details/37742423 matplotlib 是python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图.而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中. 它的文档相当完备,并且 Gallery页面 中有上百幅缩略图,打开之后都有源程序.因此如果你需要绘制某种类型的图,只需要在这个页面中浏览/复制/粘贴一下,基本上都能搞定. 在Linux下比较著名的数据图工具