• V4L2用户空间和kernel层driver的交互过程


    V4L2用户空间和kernel层driver的交互过程


    这篇文章详细分析了V4L2用户空间和kernel层driver的交互过程,目的只有一个:
    更清晰的理解V4L2视频驱动程序的系统结构,驱动编程方法,为以后开发视频驱动打好基础

    既然从用户层出发探究驱动层,这里先贴出应用层code:

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <assert.h>
    5. #include <getopt.h> 
    6. #include <fcntl.h> 
    7. #include <unistd.h>
    8. #include <errno.h>
    9. #include <sys/stat.h>
    10. #include <sys/types.h>
    11. #include <sys/time.h>
    12. #include <sys/mman.h>
    13. #include <sys/ioctl.h>
    14. #include <asm/types.h>
    15. #include <linux/videodev2.h>
    16. #include <linux/fb.h>
    17. #define CLEAR(x) memset (&(x), 0, sizeof (x))
    18.  
    19. struct buffer {
    20.     void * start;
    21.     size_t length;
    22. };
    23.  
    24. static char * dev_name = NULL;
    25. static int fd = -1;
    26. struct buffer * buffers = NULL;
    27. static unsigned int n_buffers = 0;
    28. static int time_in_sec_capture=5;
    29. static int fbfd = -1;
    30. static struct fb_var_screeninfo vinfo;
    31. static struct fb_fix_screeninfo finfo;
    32. static char *fbp=NULL;
    33. static long screensize=0;
    34.  
    35. static void errno_exit (const char * s)
    36. {
    37.     fprintf (stderr, "%s error %d, %s/n",s, errno, strerror (errno));
    38.     exit (EXIT_FAILURE);
    39. }
    40.  
    41. static int xioctl (int fd,int request,void * arg)
    42. {
    43.     int r;
    44.     /* Here use this method to make sure cmd success*/
    45.     do r = ioctl (fd, request, arg);
    46.     while (-== r && EINTR == errno);
    47.     return r;
    48. }
    49.  
    50. inline int clip(int value, int min, int max) {
    51.     return (value > max ? max : value < min ? min : value);
    52.   }
    53.  
    54. static void process_image (const void * p){
    55.     //ConvertYUVToRGB321;
    56.     unsigned char* in=(char*)p;
    57.     int width=640;
    58.     int height=480;
    59.     int istride=1280;
    60.     int x,y,j;
    61.     int y0,u,y1,v,r,g,b;
    62.     long location=0;
    63.  
    64.     for ( y = 100; y < height + 100; ++y) {
    65.         for (= 0, x=100; j < width * 2 ; j += 4,+=2) {
    66.           location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
    67.             (y+vinfo.yoffset) * finfo.line_length;
    68.             
    69.           y0 = in[j];
    70.           u = in[+ 1] - 128; 
    71.           y1 = in[+ 2]; 
    72.           v = in[+ 3] - 128; 
    73.  
    74.           r = (298 * y0 + 409 * v + 128) >> 8;
    75.           g = (298 * y0 - 100 * u - 208 * v + 128) >> 8;
    76.           b = (298 * y0 + 516 * u + 128) >> 8;
    77.         
    78.           fbp[ location + 0] = clip(b, 0, 255);
    79.           fbp[ location + 1] = clip(g, 0, 255);
    80.           fbp[ location + 2] = clip(r, 0, 255); 
    81.           fbp[ location + 3] = 255; 
    82.  
    83.           r = (298 * y1 + 409 * v + 128) >> 8;
    84.           g = (298 * y1 - 100 * u - 208 * v + 128) >> 8;
    85.           b = (298 * y1 + 516 * u + 128) >> 8;
    86.  
    87.           fbp[ location + 4] = clip(b, 0, 255);
    88.           fbp[ location + 5] = clip(g, 0, 255);
    89.           fbp[ location + 6] = clip(r, 0, 255); 
    90.           fbp[ location + 7] = 255; 
    91.           }
    92.         in +=istride;
    93.       }
    94. }
    95.  
    96. static int read_frame (void)
    97. {
    98.     struct v4l2_buffer buf;
    99.     unsigned int i;
    100.  
    101.     CLEAR (buf);
    102.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    103.     buf.memory = V4L2_MEMORY_MMAP;
    104.      /* 11. VIDIOC_DQBUF把数据放回缓存队列*/
    105.     if (-== xioctl (fd, VIDIOC_DQBUF, &buf)) {
    106.         switch (errno) {
    107.         case EAGAIN:
    108.         return 0;
    109.         case EIO: 
    110.         default:
    111.             errno_exit ("VIDIOC_DQBUF");
    112.         }
    113.     }
    114.  
    115.     assert (buf.index < n_buffers);
    116.     printf("v4l2_pix_format->field(%d)/n", buf.field);
    117.     //assert (buf.field ==V4L2_FIELD_NONE);
    118.     process_image (buffers[buf.index].start);

    119.     /*12. VIDIOC_QBUF把数据从缓存中读取出来*/
    120.     if (-== xioctl (fd, VIDIOC_QBUF, &buf))
    121.         errno_exit ("VIDIOC_QBUF");
    122.  
    123.     return 1;
    124. }
    125.  
    126. static void run (void)
    127. {
    128.     unsigned int count;
    129.     int frames;
    130.     frames = 30 * time_in_sec_capture;
    131.  
    132.     while (frames-- > 0) {
    133.         for (;;) {
    134.             fd_set fds;
    135.             struct timeval tv;
    136.             int r;
    137.             FD_ZERO (&fds);
    138.             FD_SET (fd, &fds);
    139.  
    140.             
    141.             tv.tv_sec = 2;
    142.             tv.tv_usec = 0;
    143.              /* 10. poll method*/
    144.             r = select (fd + 1, &fds, NULL, NULL, &tv);
    145.  
    146.             if (-== r) {
    147.                 if (EINTR == errno)
    148.                     continue;
    149.                 errno_exit ("select");
    150.             }
    151.  
    152.             if (== r) {
    153.                 fprintf (stderr, "select timeout/n");
    154.                 exit (EXIT_FAILURE);
    155.             }
    156.  
    157.             if (read_frame())
    158.                 break;
    159.             
    160.             }
    161.     }
    162. }
    163.  
    164. static void stop_capturing (void)
    165. {
    166.     enum v4l2_buf_type type;
    167.  
    168.     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    169.     /*13. VIDIOC_STREAMOFF结束视频显示函数*/
    170.     if (-== xioctl (fd, VIDIOC_STREAMOFF, &type))
    171.         errno_exit ("VIDIOC_STREAMOFF");
    172. }
    173.  
    174. static void start_capturing (void)
    175. {
    176.     unsigned int i;
    177.     enum v4l2_buf_type type;
    178.  
    179.     for (= 0; i < n_buffers; ++i) {
    180.         struct v4l2_buffer buf;
    181.         CLEAR (buf);
    182.  
    183.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    184.         buf.memory = V4L2_MEMORY_MMAP;
    185.         buf.index = i;
    186.          /* 8. VIDIOC_QBUF把数据从缓存中读取出来*/
    187.         if (-== xioctl (fd, VIDIOC_QBUF, &buf))
    188.             errno_exit ("VIDIOC_QBUF");
    189.     }
    190.  
    191.     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    192.      /* 9. VIDIOC_STREAMON开始视频显示函数*/
    193.     if (-== xioctl (fd, VIDIOC_STREAMON, &type))
    194.         errno_exit ("VIDIOC_STREAMON");
    195.     
    196. }
    197.  
    198. static void uninit_device (void)
    199. {
    200.     unsigned int i;
    201.  
    202.     for (= 0; i < n_buffers; ++i)
    203.         if (-== munmap (buffers[i].start, buffers[i].length))
    204.             errno_exit ("munmap");
    205.     
    206.     if (-== munmap(fbp, screensize)) {
    207.           printf(" Error: framebuffer device munmap() failed./n");
    208.           exit (EXIT_FAILURE) ;
    209.         } 
    210.     free (buffers);
    211. }
    212.  
    213.  
    214. static void init_mmap (void)
    215. {
    216.     struct v4l2_requestbuffers req;
    217.  
    218.     //mmap framebuffer
    219.     fbp = (char *)mmap(NULL,screensize,PROT_READ | PROT_WRITE,MAP_SHARED ,fbfd, 0);
    220.     if ((int)fbp == -1) {
    221.         printf("Error: failed to map framebuffer device to memory./n");
    222.         exit (EXIT_FAILURE) ;
    223.     }
    224.     memset(fbp, 0, screensize);
    225.     CLEAR (req);
    226.  
    227.     req.count = 4;
    228.     req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    229.     req.memory = V4L2_MEMORY_MMAP;
    230.      /* 6. VIDIOC_REQBUFS分配内存*/
    231.     if (-== xioctl (fd, VIDIOC_REQBUFS, &req)) {
    232.         if (EINVAL == errno) {
    233.             fprintf (stderr, "%s does not support memory mapping/n", dev_name);
    234.             exit (EXIT_FAILURE);
    235.         } else {
    236.             errno_exit ("VIDIOC_REQBUFS");
    237.         }
    238.     }
    239.  
    240.     if (req.count < 4) {
    241.         fprintf (stderr, "Insufficient buffer memory on %s/n",dev_name);
    242.         exit (EXIT_FAILURE);
    243.     }
    244.  
    245.     buffers = calloc (req.count, sizeof (*buffers));
    246.  
    247.     if (!buffers) {
    248.         fprintf (stderr, "Out of memory/n");
    249.         exit (EXIT_FAILURE);
    250.     }
    251.  
    252.     for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
    253.         struct v4l2_buffer buf;
    254.  
    255.         CLEAR (buf);
    256.  
    257.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    258.         buf.memory = V4L2_MEMORY_MMAP;
    259.         buf.index = n_buffers;
    260.          /* 7. VIDIOC_QUERYBUF把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址*/
    261.         if (-== xioctl (fd, VIDIOC_QUERYBUF, &buf))
    262.             errno_exit ("VIDIOC_QUERYBUF");
    263.  
    264.         buffers[n_buffers].length = buf.length;
    265.         buffers[n_buffers].start =mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
    266.  
    267.         if (MAP_FAILED == buffers[n_buffers].start)
    268.             errno_exit ("mmap");
    269.     }
    270.  
    271. }
    272.  
    273.  
    274.  
    275. static void init_device (void)
    276. {
    277.     struct v4l2_capability cap;
    278.     struct v4l2_cropcap cropcap;
    279.     struct v4l2_crop crop;
    280.     struct v4l2_format fmt;
    281.     unsigned int min;
    282.  
    283.  
    284.     // Get fixed screen information
    285.     if (-1==xioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
    286.      printf("Error reading fixed information./n");
    287.      exit (EXIT_FAILURE);
    288.     }
    289.  
    290.     // Get variable screen information
    291.     if (-1==xioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
    292.      printf("Error reading variable information./n");
    293.      exit (EXIT_FAILURE);
    294.     }
    295.     screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

    296.      /* 2. VIDIOC_QUERYCAP查询驱动功能*/
    297.     if (-== xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
    298.         if (EINVAL == errno) {
    299.             fprintf (stderr, "%s is no V4L2 device/n",dev_name);
    300.             exit (EXIT_FAILURE);
    301.         } else {
    302.             errno_exit ("VIDIOC_QUERYCAP");
    303.         }
    304.     }

    305.     /* Check if it is a video capture device*/
    306.     if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
    307.         fprintf (stderr, "%s is no video capture device/n",dev_name);
    308.         exit (EXIT_FAILURE);
    309.     }

    310.      /* Check if support streaming I/O ioctls*/
    311.     if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
    312.         fprintf (stderr, "%s does not support streaming i/o/n",dev_name);
    313.         exit (EXIT_FAILURE);
    314.     }
    315.  
    316.     CLEAR (cropcap);
    317.     /* Set type*/
    318.     cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    319.      /* 3. VIDIOC_CROPCAP查询驱动的修剪能力*/
    320.     /* 这里在vivi驱动中我们没有实现此方法,即不支持此操作*/
    321.     if (== xioctl (fd, VIDIOC_CROPCAP, &cropcap)) {
    322.         crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    323.         crop.= cropcap.defrect;
    324.         /* 4. VIDIOC_S_CROP设置视频信号的边框*/
    325.         /* 同样不支持这个操作*/
    326.         if (-== xioctl (fd, VIDIOC_S_CROP, &crop)) {
    327.             switch (errno) {
    328.             case EINVAL: 
    329.             break;
    330.             default:
    331.             break;
    332.             }
    333.         }
    334.     }else { }
    335.  
    336.     CLEAR (fmt);
    337.  
    338.     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    339.     fmt.fmt.pix.width = 640; 
    340.     fmt.fmt.pix.height = 480;
    341.     fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    342.     fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    343.      /* 5. VIDIOC_S_FMT设置当前驱动的频捕获格式*/
    344.     if (-== xioctl (fd, VIDIOC_S_FMT, &fmt))
    345.         errno_exit ("VIDIOC_S_FMT");
    346.  
    347.     init_mmap ();
    348.  
    349. }
    350.  
    351. static void close_device (void)
    352. {
    353.     if (-== close (fd))
    354.     errno_exit ("close");
    355.     fd = -1;
    356.     /*14. close method*/
    357.     close(fbfd);
    358. }
    359.  
    360. static void open_device (void)
    361. {
    362.     struct stat st; 
    363.  
    364.     if (-== stat (dev_name, &st)) {
    365.      fprintf (stderr, "Cannot identify '%s': %d, %s/n",dev_name, errno, strerror (errno));
    366.      exit (EXIT_FAILURE);
    367.     }
    368.  
    369.     if (!S_ISCHR (st.st_mode)) {
    370.      fprintf (stderr, "%s is no device/n", dev_name);
    371.      exit (EXIT_FAILURE);
    372.     }

    373.     fbfd = open("/dev/fb0", O_RDWR);
    374.     if (fbfd==-1) {
    375.         printf("Error: cannot open framebuffer device./n");
    376.         exit (EXIT_FAILURE);
    377.     }
    378.  
    379.     /* 1. open the char device */
    380.     fd = open (dev_name, O_RDWR| O_NONBLOCK, 0);
    381.     if (-== fd) {
    382.      fprintf (stderr, "Cannot open '%s': %d, %s/n",dev_name, errno, strerror (errno));
    383.      exit (EXIT_FAILURE);
    384.     }
    385. }
    386.  
    387. static void usage (FILE * fp,int argc,char ** argv)
    388. {
    389.     fprintf (fp,
    390.     "Usage: %s [options]/n/n"
    391.     "Options:/n"
    392.     "-d | --device name Video device name [/dev/video]/n"
    393.     "-h | --help Print this message/n"
    394.     "-t | --how long will display in seconds/n"
    395.     "",
    396.     argv[0]);
    397. }
    398.  
    399. static const char short_options [] = "d:ht:";
    400. static const struct option long_options [] = {
    401.     { "device", required_argument, NULL, 'd' },
    402.     { "help", no_argument, NULL, 'h' },
    403.     { "time", no_argument, NULL, 't' },
    404.     { 0, 0, 0, 0 }
    405. };
    406.  
    407. int main (int argc,char ** argv)
    408. {
    409.     dev_name = "/dev/video0";
    410.     for (;;) 
    411.     {
    412.         int index;
    413.         int c;
    414.  
    415.         c = getopt_long (argc, argv,short_options, long_options,&index);
    416.         if (-== c)
    417.         break;
    418.  
    419.         switch (c) {
    420.         case 0:
    421.         break;
    422.  
    423.         case 'd':
    424.         dev_name = optarg;
    425.         break;
    426.  
    427.         case 'h':
    428.         usage (stdout, argc, argv);
    429.         exit (EXIT_SUCCESS);
    430.         case 't':
    431.         time_in_sec_capture = atoi(optarg);
    432.         break;
    433.  
    434.         default:
    435.         usage (stderr, argc, argv);
    436.         exit (EXIT_FAILURE);
    437.         }
    438.     }
    439.  
    440.     open_device();
    441.     init_device();
    442.     start_capturing();
    443.     run();
    444.     stop_capturing();
    445.     uninit_device();
    446.     close_device();
    447.     exit(EXIT_SUCCESS);
    448.     return 0;
    449. }

    上面code中我已经标注出程序顺序指向的步骤1--14步,下面将一一说明应用从做这14步时驱动层是怎样响应,变化过程, 驱动加载初始化部分上一篇文章已经说过了
    正式开始取经之路哇。。。。 。。。

    STEP 1:
    fd = open (dev_name, O_RDWR| O_NONBLOCK, 0);
    打开字符设备,这个字符设备是video_device_register时创建的,code在v4l2_dev.c中,具体:

    1. static int v4l2_open(struct inode *inode, struct file *filp)
    2. {
    3.     struct video_device *vdev;
    4.     int ret = 0;

    5.     /* Check if the video device is available */
    6.     mutex_lock(&videodev_lock);
    7.     vdev = video_devdata(filp);
    8.     /* return ENODEV if the video device has already been removed. */
    9.     if (vdev == NULL || !video_is_registered(vdev)) {
    10.         mutex_unlock(&videodev_lock);
    11.         return -ENODEV;
    12.     }
    13.     /* and increase the device refcount */
    14.     video_get(vdev);
    15.     mutex_unlock(&videodev_lock);

    16.     /* 
    17.     * Here using the API you get the method you get the open() method write
    18.     * The other methods in fops use the same method to use you own code 
    19.     */
    20.     if (vdev->fops->open) {
    21.         if (vdev->lock && mutex_lock_interruptible(vdev->lock)) {
    22.             ret = -ERESTARTSYS;
    23.             goto err;
    24.         }
    25.         if (video_is_registered(vdev))
    26.             ret = vdev->fops->open(filp);
    27.         else
    28.             ret = -ENODEV;
    29.         if (vdev->lock)
    30.             mutex_unlock(vdev->lock);
    31.     }

    32. err:
    33.     /* decrease the refcount in case of an error */
    34.     if (ret)
    35.         video_put(vdev);
    36.     return ret;
    37. }

    重点在标注部分,通过这个V4L2的API调用我们自己驱动程序中定义的open方法,我们自己的open方法所属的fops是在vivi.c驱动程序的vivi_create_instance方法中 video_device_register之前关联进来的

    1. int v4l2_fh_open(struct file *filp)
    2. {
    3.     struct video_device *vdev = video_devdata(filp);
    4.     struct v4l2_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL);

    5.     /*
    6.     * IN the open method, do only one job
    7.     * set v4l2_fh into filp->private_data for later use, and initial v4l2_fh
    8.     */
    9.     filp->private_data = fh;
    10.     if (fh == NULL)
    11.         return -ENOMEM;
    12.     v4l2_fh_init(fh, vdev);
    13.     v4l2_fh_add(fh);
    14.     return 0;
    15. }
    16. EXPORT_SYMBOL_GPL(v4l2_fh_open);

    这个open方法只是初始化了一个v4l2_fh,并关联到filp->private中,方便以后使用
    这里设置V4L2_FL_USES_V4L2_FH这个标志位,设置优先级为UNSET,如果我们的自己驱动程序实现了,支持
    VIDIOC_SUBSCRIBE_EVENT,那么v4l2_event_init,在events初始化中初始化链表,并设置sequence为-1,如果不支持,则设置fh->events为NULL
    最后add list

    STEP 2:
    if (-1 == xioctl (fd, VIDIOC_QUERYCAP, &cap))
    这么调用完成下面过程,不行的从驱动层获取cap。直到成功拿到我们想要的数据

    1. static int xioctl (int fd,int request,void * arg)
    2. {
    3.     int r;
    4.     /* Here use this method to make sure cmd success*/
    5.     do r = ioctl (fd, request, arg);
    6.     while (-== r && EINTR == errno);
    7.     return r;
    8. }

    也就是调用驱动层的ioctl方法,从v4l2 api中的ictol 调用我们自己定义的ioctl ,这中间的过程不在多做说明,我们自己的驱动的控制过程由v4l2_ioctl.c这个文件中的方法实现,一个很庞大的switch
    值得一提的是,慢慢后面你会明白的,这里 v4l2_ioctl.c这个文件中的方法实现其实只是会中转站,它接着就回调了我们自己驱动程序中定义的控制接口,后面再说吧

    1. long video_ioctl2(struct file *file,
    2.      unsigned int cmd, unsigned long arg)
    3. {
    4.     return video_usercopy(file, cmd, arg, __video_do_ioctl);
    5. }

    这里这个 __video_do_ioctl 方法其实完全做了我们所有的控制过程,又为什么又要经过video_usercopy这个方法呢,不妨看一看这个方法

    1. long
    2. video_usercopy(struct file *file, unsigned int cmd, unsigned long arg,
    3.      v4l2_kioctl func)
    4. {
    5.     char    sbuf[128];
    6.     void *mbuf = NULL;
    7.     void    *parg = (void *)arg;
    8.     long    err = -EINVAL;
    9.     bool    has_array_args;
    10.     size_t array_size = 0;
    11.     void __user *user_ptr = NULL;
    12.     void    **kernel_ptr = NULL;

    13.     /* Copy arguments into temp kernel buffer */
    14.     if (_IOC_DIR(cmd) != _IOC_NONE) {
    15. ........这里检查128个字节的大小是否够存放用户端发送来的数据,不够则需要重新申请一个新的内存用来存放,指向parg这个地址
    16.         if (_IOC_SIZE(cmd) <= sizeof(sbuf)) {
    17.             parg = sbuf;
    18.         } else {
    19.             /* too big to allocate from stack */
    20.             mbuf = kmalloc(_IOC_SIZE(cmd), GFP_KERNEL);
    21.             if (NULL == mbuf)
    22.                 return -ENOMEM;
    23.             parg = mbuf;
    24.         }

    25.         err = -EFAULT;
    26.         if (_IOC_DIR(cmd) & _IOC_WRITE) {
    27.             unsigned long n = cmd_input_size(cmd);

    28.             if (copy_from_user(parg, (void __user *)arg, n))
    29.                 goto out;

    30.             /* zero out anything we don't copy from userspace */
    31.             if (< _IOC_SIZE(cmd))
    32.                 memset((u8 *)parg + n, 0, _IOC_SIZE(cmd) - n);
    33.         } else {
    34.             /* read-only ioctl */
    35.             memset(parg, 0, _IOC_SIZE(cmd));
    36.         }
    37.     }
    38. ....check
    39.     err = check_array_args(cmd, parg, &array_size, &user_ptr, &kernel_ptr);
    40.     if (err < 0)
    41.         goto out;
    42.     has_array_args = err;
    43. ....这里这块如果用户端有数据写到kernel,这里负责数据拷贝
    44.     if (has_array_args) {
    45.         /*
    46.          * When adding new types of array args, make sure that the
    47.          * parent argument to ioctl (which contains the pointer to the
    48.          * array) fits into sbuf (so that mbuf will still remain
    49.          * unused up to here).
    50.          */
    51.         mbuf = kmalloc(array_size, GFP_KERNEL);
    52.         err = -ENOMEM;
    53.         if (NULL == mbuf)
    54.             goto out_array_args;
    55.         err = -EFAULT;
    56.         if (copy_from_user(mbuf, user_ptr, array_size))
    57.             goto out_array_args;
    58.         *kernel_ptr = mbuf;
    59.     }

    60.     /* Handles IOCTL */
    61.     err = func(file, cmd, parg);
    62.     if (err == -ENOIOCTLCMD)
    63.         err = -EINVAL;
    1.     if (has_array_args) {
    2.         *kernel_ptr = user_ptr;
    3.         if (copy_to_user(user_ptr, mbuf, array_size))
    4.             err = -EFAULT;
    5.         goto out_array_args;
    6.     }
    7.     if (err < 0)
    8.         goto out;

    9. out_array_args:
    10.     /* Copy results into user buffer */
    11.     switch (_IOC_DIR(cmd)) {
    12.     case _IOC_READ:
    13.     case (_IOC_WRITE | _IOC_READ):
    14.         if (copy_to_user((void __user *)arg, parg, _IOC_SIZE(cmd)))
    15.             err = -EFAULT;
    16.         break;
    17.     }

    18. out:
    19.     kfree(mbuf);
    20.     return err;
    21. }
    22. EXPORT_SYMBOL(video_usercopy);

    自我感觉这个方法还是有很多精妙之处的,主要的控制过程是在我标注的地方调用完成的,这个调用之前做check动作,检查用户端发来的命令是否合法,
    最重要的是把用户端的数据copy到kernel 端;而这个调用之后,则是我们处理完我们的动作之后,我们在这里吧用户端请求的数据从kernel 端copy到用户端
    这样做的好处是显而易见的,任务明确,控制只做控制,用户空间和kernel空间数据的copy在所有控制之前,控制之后进行
    以上动作做完之后,进入庞大的控制中枢,这来开始至贴出具体到某一个控制的代码,否则code过大,不易分析:

    1. case VIDIOC_QUERYCAP://查询视频设备的功能
    2.     {
    3.         struct v4l2_capability *cap = (struct v4l2_capability *)arg;

    4.         if (!ops->vidioc_querycap)
    5.             break;

    6.         ret = ops->vidioc_querycap(file, fh, cap);
    7.         if (!ret)/* i don't think here need to check */
    8.             dbgarg(cmd, "driver=%s, card=%s, bus=%s, "
    9.                     "version=0x%08x, "
    10.                     "capabilities=0x%08x\n",
    11.                     cap->driver, cap->card, cap->bus_info,
    12.                     cap->version,
    13.                     cap->capabilities);
    14.         break;
    15.     }

    这来调用了我们自己驱动中填充的v4l2_ioctl_ops结构体,从这里开始,我上面说到的话得到了验证,这就是linux 中API 的强大之处
    作为中间层的这个控制中枢又回调驱动自己定义编写的控制

    1. /* ------------------------------------------------------------------
    2.     IOCTL vidioc handling
    3.    ------------------------------------------------------------------*/
    4. static int vidioc_querycap(struct file *file, void *priv,
    5.                     struct v4l2_capability *cap)
    6. {
    7.     struct vivi_dev *dev = video_drvdata(file);

    8.     strcpy(cap->driver, "vivi");
    9.     strcpy(cap->card, "vivi");
    10.     strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
    11.     cap->version = VIVI_VERSION;
    12.     cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | \
    13.              V4L2_CAP_READWRITE;
    14.     return 0;
    15. }

    这来做的事情很简单,只是将配置信息保存到cap这个变量中,之后上传给用户空间

    STEP 3:
    /* 3. VIDIOC_CROPCAP查询驱动的修剪能力*/
    /* 这里在vivi 驱动中我们没有实现此方法,即不支持此操作*/
    if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap))
    这个判断在中间层控制中枢中进行的,check到我们自己的驱动中没有这个控制功能的支持
    所以这里的 STEP 4同样不会进行

    STEP 5:
    /* 5. VIDIOC_S_FMT设置当前驱动的频捕获格式*/
    if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
    对应到控制中心是这样的

    1. case VIDIOC_S_FMT:
    2.     {
    3.         struct v4l2_format *= (struct v4l2_format *)arg;

    4.         /* FIXME: Should be one dump per type */
    5.         dbgarg(cmd, "type=%s\n", prt_names(f->type, v4l2_type_names));

    6.         switch (f->type) {
    7.         case V4L2_BUF_TYPE_VIDEO_CAPTURE:
    8.             CLEAR_AFTER_FIELD(f, fmt.pix);
    9.             v4l_print_pix_fmt(vfd, &f->fmt.pix);
    10.             if (ops->vidioc_s_fmt_vid_cap) {
    11.                 ret = ops->vidioc_s_fmt_vid_cap(file, fh, f);
    12.             } else if (ops->vidioc_s_fmt_vid_cap_mplane) {
    13.                 if (fmt_sp_to_mp(f, &f_copy))
    14.                     break;
    15.                 ret = ops->vidioc_s_fmt_vid_cap_mplane(file, fh,
    16.                                     &f_copy);
    17.                 if (ret)
    18.                     break;

    19.                 if (f_copy.fmt.pix_mp.num_planes > 1) {
    20.                     /* Drivers shouldn't adjust from 1-plane
    21.                      * to more than 1-plane formats */
    22.                     ret = -EBUSY;
    23.                     WARN_ON(1);
    24.                     break;
    25.                 }

    26.                 ret = fmt_mp_to_sp(&f_copy, f);
    27.             }
    28.             break;
    29.         case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
    30.             CLEAR_AFTER_FIELD(f, fmt.pix_mp);
    31.             v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp);
    32.             if (ops->vidioc_s_fmt_vid_cap_mplane) {
    33.                 ret = ops->vidioc_s_fmt_vid_cap_mplane(file,
    34.                                     fh, f);
    35.             } else if (ops->vidioc_s_fmt_vid_cap &&
    36.                     f->fmt.pix_mp.num_planes == 1) {
    37.                 if (fmt_mp_to_sp(f, &f_copy))
    38.                     break;
    39.                 ret = ops->vidioc_s_fmt_vid_cap(file,
    40.                                 fh, &f_copy);
    41.                 if (ret)
    42.                     break;

    43.                 ret = fmt_sp_to_mp(&f_copy, f);
    44.             }
    45.             break;
    46.         case V4L2_BUF_TYPE_VIDEO_OVERLAY:
    47.             CLEAR_AFTER_FIELD(f, fmt.win);
    48.             if (ops->vidioc_s_fmt_vid_overlay)
    49.                 ret = ops->vidioc_s_fmt_vid_overlay(file,
    50.                                  fh, f);
    51.             break;
    52.         case V4L2_BUF_TYPE_VIDEO_OUTPUT:
    53.             CLEAR_AFTER_FIELD(f, fmt.pix);
    54.             v4l_print_pix_fmt(vfd, &f->fmt.pix);
    55.             if (ops->vidioc_s_fmt_vid_out) {
    56.                 ret = ops->vidioc_s_fmt_vid_out(file, fh, f);
    57.             } else if (ops->vidioc_s_fmt_vid_out_mplane) {
    58.                 if (fmt_sp_to_mp(f, &f_copy))
    59.                     break;
    60.                 ret = ops->vidioc_s_fmt_vid_out_mplane(file, fh,
    61.                                     &f_copy);
    62.                 if (ret)
    63.                     break;

    64.                 if (f_copy.fmt.pix_mp.num_planes > 1) {
    65.                     /* Drivers shouldn't adjust from 1-plane
    66.                      * to more than 1-plane formats */
    67.                     ret = -EBUSY;
    68.                     WARN_ON(1);
    69.                     break;
    70.                 }

    71.                 ret = fmt_mp_to_sp(&f_copy, f);
    72.             }
    73.             break;
    74.         case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
    75.             CLEAR_AFTER_FIELD(f, fmt.pix_mp);
    76.             v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp);
    77.             if (ops->vidioc_s_fmt_vid_out_mplane) {
    78.                 ret = ops->vidioc_s_fmt_vid_out_mplane(file,
    79.                                     fh, f);
    80.             } else if (ops->vidioc_s_fmt_vid_out &&
    81.                     f->fmt.pix_mp.num_planes == 1) {
    82.                 if (fmt_mp_to_sp(f, &f_copy))
    83.                     break;
    84.                 ret = ops->vidioc_s_fmt_vid_out(file,
    85.                                 fh, &f_copy);
    86.                 if (ret)
    87.                     break;

    88.                 ret = fmt_mp_to_sp(&f_copy, f);
    89.             }
    90.             break;
    91.         case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
    92.             CLEAR_AFTER_FIELD(f, fmt.win);
    93.             if (ops->vidioc_s_fmt_vid_out_overlay)
    94.                 ret = ops->vidioc_s_fmt_vid_out_overlay(file,
    95.                     fh, f);
    96.             break;
    97.         case V4L2_BUF_TYPE_VBI_CAPTURE:
    98.             CLEAR_AFTER_FIELD(f, fmt.vbi);
    99.             if (ops->vidioc_s_fmt_vbi_cap)
    100.                 ret = ops->vidioc_s_fmt_vbi_cap(file, fh, f);
    101.             break;
    102.         case V4L2_BUF_TYPE_VBI_OUTPUT:
    103.             CLEAR_AFTER_FIELD(f, fmt.vbi);
    104.             if (ops->vidioc_s_fmt_vbi_out)
    105.                 ret = ops->vidioc_s_fmt_vbi_out(file, fh, f);
    106.             break;
    107.         case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
    108.             CLEAR_AFTER_FIELD(f, fmt.sliced);
    109.             if (ops->vidioc_s_fmt_sliced_vbi_cap)
    110.                 ret = ops->vidioc_s_fmt_sliced_vbi_cap(file,
    111.                                     fh, f);
    112.             break;
    113.         case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
    114.             CLEAR_AFTER_FIELD(f, fmt.sliced);
    115.             if (ops->vidioc_s_fmt_sliced_vbi_out)
    116.                 ret = ops->vidioc_s_fmt_sliced_vbi_out(file,
    117.                                     fh, f);
    118.             break;
    119.         case V4L2_BUF_TYPE_PRIVATE:
    120.             /* CLEAR_AFTER_FIELD(f, fmt.raw_data); <- does nothing */
    121.             if (ops->vidioc_s_fmt_type_private)
    122.                 ret = ops->vidioc_s_fmt_type_private(file,
    123.                                 fh, f);
    124.             break;
    125.         }
    126.         break;
    127.     }

    以后根据不同的type 决定了我们自己驱动程序中不同的控制实现,这个type是根据用户空间的设置而定的,还包括其他几个参数,如下:

    1. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    2. fmt.fmt.pix.width = 640; 
    3. fmt.fmt.pix.height = 480;
    4. fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    5. fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    这里根据设定的type,所以驱动程序的处理过程如下:

    1. static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
    2.                     struct v4l2_format *f)
    3. {
    4.     struct vivi_dev *dev = video_drvdata(file);
    5.     struct vb2_queue *= &dev->vb_vidq;
    6. ....在下面这个函数中,做了一些试探性的动作,如果试探失败则下面不会赋值,试探通过则后续正常设置即可,在这个试探函数中同时做了一些设置动作
    7.     int ret = vidioc_try_fmt_vid_cap(file, priv, f);
    8.     if (ret < 0)
    9.         return ret;

    10.     if (vb2_is_streaming(q)) {
    11.         dprintk(dev, 1, "%s device busy\n", __func__);
    12.         return -EBUSY;
    13.     }
    14. ....按用户空间需求设置
    15.     dev->fmt = get_format(f);
    16.     dev->width = f->fmt.pix.width;
    17.     dev->height = f->fmt.pix.height;
    18.     dev->field = f->fmt.pix.field;

    19.     return 0;
    20. }


    STEP6 :
    /* 6. VIDIOC_REQBUFS分配内存*/
    if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req))
    中间层控制中枢:

    1. case VIDIOC_REQBUFS:
    2.     {
    3.         struct v4l2_requestbuffers *= arg;

    4.         if (!ops->vidioc_reqbufs)
    5.             break;
    6. ........这个方法check 驱动必须实现了fmt方法,看具体看代码
    7.         ret = check_fmt(ops, p->type);
    8.         if (ret)
    9.             break;

    10.         if (p->type < V4L2_BUF_TYPE_PRIVATE)
    11.             CLEAR_AFTER_FIELD(p, memory);

    12.         ret = ops->vidioc_reqbufs(file, fh, p);
    13.         dbgarg(cmd, "count=%d, type=%s, memory=%s\n",
    14.                 p->count,
    15.                 prt_names(p->type, v4l2_type_names),
    16.                 prt_names(p->memory, v4l2_memory_names));
    17.         break;
    18.     }

    驱动中实现:

    1. static int vidioc_reqbufs(struct file *file, void *priv,
    2.              struct v4l2_requestbuffers *p)
    3. {
    4.     struct vivi_dev *dev = video_drvdata(file);
    5.     return vb2_reqbufs(&dev->vb_vidq, p);
    6. }

    到了这里来到了这个全新的话题,实现
    vb2_reqbufs ( & dev - > vb_vidq ,  p ) ;
    这里暂且不讨论这个方法,相对较复杂,待日后研究,先把注释部分放到这里,包括其他内存操作,之后深入研究补充,专门作为一篇整理
    /**
     * Should be called from vidioc_reqbufs ioctl handler of a driver.
     * This function:
     * 1) verifies streaming parameters passed from the userspace,
     * 2) sets up the queue,
     * 3) negotiates number of buffers and planes per buffer with the driver to be used during streaming,
     * 4) allocates internal buffer structures (struct vb2_buffer), according to the agreed parameters,
     * 5) for MMAP memory type, allocates actual video memory, using the memory handling/allocation routines provided during queue initialization
     * If req->count is 0, all the memory will be freed instead.
     * If the queue has been allocated previously (by a previous vb2_reqbufs) call
     * and the queue is not busy, memory will be reallocated.
     * The return values from this function are intended to be directly returned from vidioc_reqbufs handler in driver.
     */

    STEP 7:
    /* 7. VIDIOC_QUERYBUF把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址*/
    if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
    中间层控制中枢:

    1. case VIDIOC_QUERYBUF:
    2.     {
    3.         struct v4l2_buffer *= arg;

    4.         if (!ops->vidioc_querybuf)
    5.             break;
    6.         ret = check_fmt(ops, p->type);
    7.         if (ret)
    8.             break;

    9.         ret = ops->vidioc_querybuf(file, fh, p);
    10.         if (!ret)
    11.             dbgbuf(cmd, vfd, p);
    12.         break;
    13.     }

    驱动中控制实现:

    1. static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
    2. {
    3.     struct vivi_dev *dev = video_drvdata(file);
    4.     return vb2_querybuf(&dev->vb_vidq, p);
    5. }

    /**
     * Should be called from vidioc_querybuf ioctl handler in driver.
     * This function will verify the passed v4l2_buffer structure and fill the
     * relevant information for the userspace.
     * The return values from this function are intended to be directly returned from vidioc_querybuf handler in driver.
     */

    STEP 8:
    /* 8. VIDIOC_QBUF把数据从缓存中读取出来*/
    if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
    中间层控制中枢:

    1. case VIDIOC_QBUF:
    2.     {
    3.         struct v4l2_buffer *= arg;

    4.         if (!ops->vidioc_qbuf)
    5.             break;
    6.         ret = check_fmt(ops, p->type);
    7.         if (ret)
    8.             break;

    9.         ret = ops->vidioc_qbuf(file, fh, p);
    10.         if (!ret)
    11.             dbgbuf(cmd, vfd, p);
    12.         break;
    13.     }

    驱动中控制实现:

    1. static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    2. {
    3.     struct vivi_dev *dev = video_drvdata(file);
    4.     return vb2_qbuf(&dev->vb_vidq, p);
    5. }

    /**
     * Should be called from vidioc_qbuf ioctl handler of a driver.
     * This function:
     * 1) verifies the passed buffer,
     * 2) calls buf_prepare callback in the driver (if provided), in which driver-specific buffer initialization can be performed,
     * 3) if streaming is on, queues the buffer in driver by the means of buf_queue callback for processing.
     * The return values from this function are intended to be directly returned from vidioc_qbuf handler in driver.
     */

    STEP 9:
    /* 9. VIDIOC_STREAMON开始视频显示函数*/
    if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
    中间层控制中枢:

    1. case VIDIOC_STREAMON:
    2.     {
    3.         enum v4l2_buf_type i = *(int *)arg;

    4.         if (!ops->vidioc_streamon)
    5.             break;
    6.         dbgarg(cmd, "type=%s\n", prt_names(i, v4l2_type_names));
    7.         ret = ops->vidioc_streamon(file, fh, i);
    8.         break;
    9.     }

    驱动控制实现;

    1. static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
    2. {
    3.     struct vivi_dev *dev = video_drvdata(file);
    4.     return vb2_streamon(&dev->vb_vidq, i);
    5. }

    /**
     * Should be called from vidioc_streamon handler of a driver.
     * This function:
     * 1) verifies current state
     * 2) starts streaming and passes any previously queued buffers to the driver
     * The return values from this function are intended to be directly returned from vidioc_streamon handler in the driver.
     */

    STEP 10:
    /* 10. poll method*/
    select (fd + 1, &fds, NULL, NULL, &tv);
    从V4L2驱动API开始:

    1. static unsigned int v4l2_poll(struct file *filp, struct poll_table_struct *poll)
    2. {
    3.     struct video_device *vdev = video_devdata(filp);
    4.     int ret = POLLERR | POLLHUP;

    5.     if (!vdev->fops->poll)
    6.         return DEFAULT_POLLMASK;
    7.     if (vdev->lock)
    8.         mutex_lock(vdev->lock);
    9.     if (video_is_registered(vdev))
    10.         ret = vdev->fops->poll(filp, poll);
    11.     if (vdev->lock)
    12.         mutex_unlock(vdev->lock);
    13.     return ret;
    14. }

    驱动实现:

    1. static unsigned int
    2. vivi_poll(struct file *file, struct poll_table_struct *wait)
    3. {
    4.     struct vivi_dev *dev = video_drvdata(file);
    5.     struct vb2_queue *= &dev->vb_vidq;

    6.     dprintk(dev, 1, "%s\n", __func__);
    7.     return vb2_poll(q, file, wait);
    8. }

    /**
     * This function implements poll file operation handler for a driver.
     * For CAPTURE queues, if a buffer is ready to be dequeued, the userspace will be informed that the file descriptor of a video device is available for reading.
     * For OUTPUT queues, if a buffer is ready to be dequeued, the file descriptor will be reported as available for writing.
     * The return values from this function are intended to be directly returned from poll handler in driver.
     */

    STEP 11:
    /* 11. VIDIOC_DQBUF把数据放回缓存队列*/
    if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf))
    中间层控制中枢:

    1. case VIDIOC_DQBUF:
    2.     {
    3.         struct v4l2_buffer *= arg;

    4.         if (!ops->vidioc_dqbuf)
    5.             break;
    6.         ret = check_fmt(ops, p->type);
    7.         if (ret)
    8.             break;

    9.         ret = ops->vidioc_dqbuf(file, fh, p);
    10.         if (!ret)
    11.             dbgbuf(cmd, vfd, p);
    12.         break;
    13.     }

    驱动控制实现:

    1. static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    2. {
    3.     struct vivi_dev *dev = video_drvdata(file);
    4.     return vb2_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK);
    5. }

    /**
     * Should be called from vidioc_dqbuf ioctl handler of a driver.
     * This function:
     * 1) verifies the passed buffer,
     * 2) calls buf_finish callback in the driver (if provided), in which driver can perform any additional operations that may be required before returning the buffer to userspace, such as cache sync,
     * 3) the buffer struct members are filled with relevant information for the userspace.
     * The return values from this function are intended to be directly returned from vidioc_dqbuf handler in driver.
     */

    STEP 12:
    /*12. VIDIOC_QBUF把数据从缓存中读取出来*/
    if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
    中间层控制中枢:

    1. case VIDIOC_QBUF:
    2.     {
    3.         struct v4l2_buffer *= arg;

    4.         if (!ops->vidioc_qbuf)
    5.             break;
    6.         ret = check_fmt(ops, p->type);
    7.         if (ret)
    8.             break;

    9.         ret = ops->vidioc_qbuf(file, fh, p);
    10.         if (!ret)
    11.             dbgbuf(cmd, vfd, p);
    12.         break;
    13.     }

    驱动控制实现:

    1. static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    2. {
    3.     struct vivi_dev *dev = video_drvdata(file);
    4.     return vb2_qbuf(&dev->vb_vidq, p);
    5. }


    STEP 13:
    /*13. VIDIOC_STREAMOFF结束视频显示函数*/
    if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type))
    中间层控制中枢:

    1. case VIDIOC_STREAMOFF:
    2.     {
    3.         enum v4l2_buf_type i = *(int *)arg;

    4.         if (!ops->vidioc_streamoff)
    5.             break;
    6.         dbgarg(cmd, "type=%s\n", prt_names(i, v4l2_type_names));
    7.         ret = ops->vidioc_streamoff(file, fh, i);
    8.         break;
    9.     }

    驱动控制实现:

    1. static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
    2. {
    3.     struct vivi_dev *dev = video_drvdata(file);
    4.     return vb2_streamoff(&dev->vb_vidq, i);
    5. }


    STEP 13:
    /*13. VIDIOC_STREAMOFF结束视频显示函数*/
    if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type))
    中间层控制中枢:

    1. case VIDIOC_STREAMOFF:
    2.     {
    3.         enum v4l2_buf_type i = *(int *)arg;

    4.         if (!ops->vidioc_streamoff)
    5.             break;
    6.         dbgarg(cmd, "type=%s\n", prt_names(i, v4l2_type_names));
    7.         ret = ops->vidioc_streamoff(file, fh, i);
    8.         break;
    9.     }

    驱动控制实现:

    1. static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
    2. {
    3.     struct vivi_dev *dev = video_drvdata(file);
    4.     return vb2_streamoff(&dev->vb_vidq, i);
    5. }


    STEP 14:
    /*14. close method*/
    close(fbfd);

    1. static int v4l2_release(struct inode *inode, struct file *filp)
    2. {
    3.     struct video_device *vdev = video_devdata(filp);
    4.     int ret = 0;

    5.     if (vdev->fops->release) {
    6.         if (vdev->lock)
    7.             mutex_lock(vdev->lock);
    8.         vdev->fops->release(filp);
    9.         if (vdev->lock)
    10.             mutex_unlock(vdev->lock);
    11.     }
    12.     /* decrease the refcount unconditionally since the release()
    13.      return value is ignored. */
    14.     video_put(vdev);
    15.     return ret;
    16. }
    1. static int vivi_close(struct file *file)
    2. {
    3.     struct video_device *vdev = video_devdata(file);
    4.     struct vivi_dev *dev = video_drvdata(file);

    5.     dprintk(dev, 1, "close called (dev=%s), file %p\n",
    6.         video_device_node_name(vdev), file);

    7.     if (v4l2_fh_is_singular_file(file))
    8.         vb2_queue_release(&dev->vb_vidq);
    9.     return v4l2_fh_release(file);
    10. }

    到此为止,整个过程算是基本完结了,不过其中videobuf2_core.c 在我看来自己必须专门钻研一下了
    videobuf2_core .c 是视频数据传输的核心
    也可以说是视频驱动的重中之重
    待续。。。。。。

  • 相关阅读:
    SharePoint 2016 图文安装教程
    CSS选择器的特殊性和LOVE HA
    CSS相邻兄弟选择器
    Javascript高级程序设计——函数
    Javascript高级程序设计——基本概念(二)
    Javascript高级程序设计——基本概念(一)
    Javascript高级程序设计——在HTML中使用Javascript
    Javascript高级程序设计——javascript简介
    Javascript包含对象的数组去重
    JQuery阻止表单提交的方法总结
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3037446.html
Copyright © 2020-2023  润新知