2五、寫一個USB攝像頭驅動程序(有ioctrl分析)

videobuf2-core.h中的vb2_buffer,記錄了v4l2_buffer ,驅動能夠對vb2_buffer的v4l2_buffer進行操控,
 
vb2_buffer是v4l2框架層的代碼,而v4l2_buffer也是用戶空間的應用程序,能夠讀取的一個數據結構。

在reqbuf的時候把每一個vb2_buffer會放入vb2_queue中,經過vb2_buffer的index在數組中保存

uvc_init -> uvc_driver.cgit

    usb_register ->    //註冊到usb core
        uvc_probe ->    //攝像頭插入,枚舉成功後被調用
            uvc_parse_control -> //解析除了四大類描述符以外的全部描述符,四大類描述符在usb枚舉的時候已經解析過了.
                uvc_parse_vendor_control    //解析vendor-specific控制描述符,這裏貌似只有Logitech廠商的.
                uvc_parse_standard_control ->    //包括VS, 全部的entities,好比IT,OT,PU,SU等, Unit和Terminal會添加到uvc_device的entities鏈表.
                    uvc_parse_streaming    -> //先解析VC HEADER,即Class-Specific VC Interface Descriptor
                    //, 裏面會包含VC的總長度,當前版本,以及有幾個VS, 而後循環調用uvc_parse_streaming解析VS.
                    //包括VS HEADER, frame, format等, 主要填充uvc_streaming結構體.最終被添加到uvc_device的streams鏈表.
                        uvc_parse_format    //解析format描述符.
            v4l2_device_register    //註冊成爲標準的v4l2 device,對於Camera HAL來講,uvc和通常camera是同樣的行爲.
            uvc_ctrl_init_device ->    //根據前面獲得的不一樣的entities相關信息都放到struct uvc_control中管理.
                uvc_ctrl_init_ctrl    //從硬件獲得的CTRL和代碼中原先的配置和mappming作匹配,而後添加到uvc_device中.
            uvc_scan_device     -> //針對不是OT的entities建立struct uvc_video_chain並綁定起來.
                uvc_scan_chain    //entity和video chain綁定.
                list_add_tail    //添加到uvc device中管理.
            uvc_register_chains ->    根據前面獲得的chain後進行註冊
                uvc_register_terms ->    //只有type是UVC_TT_STREAMING的才能註冊,也就是說一個stream對應註冊一個video.
                    uvc_register_video ->    
                        uvc_video_init
                            uvc_queue_init    //video buffer queue初始化
                   uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);//設置queue的各個操做函數,會在reqsbuf時候用來分配buf
                            usb_set_interface
                 video_set_drvdata(vdev, stream);//很是重要:這裏把stream和video_device真的關聯起來,open的時候找到這個會根據設備號找到video_device,在找到stream,這個結構體來ioctrl中都會被用到
                            uvc_get_video_ctrl    //把設備的配置做爲stream control的默認參數.
                        video_device_alloc
                        video_register_device    //註冊的v4l2 device對應的ops是uvc_fops
            uvc_status_init    //USB input/urb相關內容初始化
                uvc_input_init    -> //camera是做爲一個input設備存在的.
                    input_allocate_device
                    input_register_device
                usb_alloc_urb
                usb_rcvintpipe        //建立接收中斷endpoint.
                usb_fill_int_urb    //urb填充

1.構造一個usb_driver
2.設置
  probe:
    2.1. 分配video_device:video_device_alloc
    2.2. 設置
      .fops
      .ioctl_ops (裏面須要設置11項)
      若是要用內核提供的緩衝區操做函數,還須要構造一個videobuf_queue_ops
    2.3. 註冊: video_register_device
  id_table: 表示支持哪些USB設備
3.註冊: usb_register數組

註冊成功後,當usb設備插入時致使hub_irq被調用,會喚醒休眠的線程,線程中執行hub_event函數,會分配設置usb_device,組成設備的時候調用總線的match函數去匹配id_table,複合時調用driver的probe函數,uvc_driver驅動在probe函數中會頂層是uvc_device結構體,裏面的steams鏈表保存VideoStreamingInterfaces接口,entities和chains保存VideoControlInterfaces接口,二者都在uvc_parse_control(解析除了四大類描述符以外的全部描述符,四大類描述符在usb枚舉的時候已經解析過了.)函數中被掛接各個結構體,chains鏈表上掛接uvc_video_chain結構體,其與entities中每一個entity相關聯,steams鏈表掛接uvc_device裏的streams鏈表上,在streams這個結構體裏面有當前接口的format格式和frame以及struct uvc_video_queue queue結構體,其有個vb2_queue,裏面有緩衝區地址和長度信息,還有各個函數用來分配buf(具體見上面代碼分析)

(UVC規格,通常即插即用的USB攝像頭都複合該規則)
UVC: USB Video Class
UVC驅動:drivers\media\video\uvc\緩存

uvc_driver.c分析:
1. usb_register(&uvc_driver.driver);
2. uvc_probe
   uvc_register_video數據結構

    uvc_video_initapp

      uvc_queue_init//初始化隊列,提供操做隊列的函數
    vdev = video_device_alloc();
    vdev->fops = &uvc_fops;
    video_register_device框架

在www.usb.org下載 uvc specification,
UVC 1.5 Class specification.pdf : 有詳細描述
USB_Video_Example 1.5.pdf : 有示例ide

基於嵌入式Linux的視頻採集系統---UVC驅動模型介紹 - BEYOND ME - BEYOND ME

經過VideoControl Interface來控制,通常只有一個
經過VideoStreaming Interface來讀視頻數據,通常有一個或者多個
VC裏含有多個Unit/Terminal等功能模塊,能夠經過訪問這些模塊進行控制,好比調亮度函數

SU:select Unit  PU:process Unnit,還有一些其餘Unit,能夠從UVC 1.5 Class specification.pdf中看到this

IT:input terminal CT:Camera Terminal OT:out terminalspa

Terminal用於‘內‘’外’連接,Unit是裏面的功能模塊

Terminal和Unit在代碼中被統稱爲entity

PU用於控制圖像的屬性
分析UVC驅動調用過程:
const struct v4l2_file_operations uvc_fops = {
  .owner = THIS_MODULE,
  .open = uvc_v4l2_open,
  .release = uvc_v4l2_release,
  .ioctl = uvc_v4l2_ioctl,
  .read = uvc_v4l2_read,
  .mmap = uvc_v4l2_mmap,
  .poll = uvc_v4l2_poll,
};

1. open:
  uvc_v4l2_open

    stream = video_drvdata(file);

      video_get_drvdata(video_devdata(file));//video_devdata(file)就是找到的video_device

        dev->p->driver_data  //返回的就是streams,p是device_private指針

(uvc_v4l2_ioctl->video_usercopy->uvc_v4l2_do_ioctl(這個函數在video_usercopy的參數中被使用))
2. VIDIOC_QUERYCAP // video->streaming->type 應該是在設備被枚舉時分析描述符時設置的
  if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |  V4L2_CAP_STREAMING;
  else
    cap->capabilities = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
3. VIDIOC_ENUM_FMT // format數組應是在設備被枚舉時設置的
  format = &video->streaming->format[fmt->index];(video->streaming能夠理解爲上圖中的VideoStreaming Interface)
4. VIDIOC_G_FMT
  uvc_v4l2_get_format // USB攝像頭支持多種格式fromat, 每種格式下有多種frame(好比分辨率)
    struct uvc_format *format = video->streaming->cur_format;(cur_format應是在設備被枚舉時設置的)
    struct uvc_frame *frame = video->streaming->cur_frame;
5. VIDIOC_TRY_FMT
  uvc_v4l2_try_format
    /* Check if the hardware supports the requested format. */找到是否支持format

    /* Find the closest image size. The distance between image sizes is//找到接近的frame
    * the size in pixels of the non-overlapping regions between the
    * requested size and the frame-specified size.
    */
6. VIDIOC_S_FMT // 只是把參數保存起來,尚未發給USB攝像頭
  uvc_v4l2_set_format
    uvc_v4l2_try_format
      streaming->cur_format = format;
      streaming->cur_frame = frame;
7. VIDIOC_REQBUFS //僅分配了vb2_buffer的大小,存數據的buf沒有分配(應該是分配了,這裏寫沒有分配應該是受到分析vivi時的影響,能夠參考第七課咱們本身寫的uvc驅動程序)

           //同時,這裏會調用queue的alloc函數給全部的planes分配空間,,後面mmap會經過__find_plane_by_offset函數負責根據用戶空間提供的offset來找到對應的struct vb2_buffer *vb,對於V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE類型的視頻捕獲設備num_planes能夠理解爲緩衝buffer由多少個plane組成,其vb->v4l2_buf.length = num_planes;,視頻數據應該真正存放在planes中,由vb2_buffer中的planes數組來保存,並經過__vb2_buf_mem_alloc給其分配空間,每一個planes的大小保存在vb2_queue中,而且會保存在v4l2_planes中(對於非Capture_Mplane設備,緩衝buffer的length和offset都保存在vb2_buffer中的v4l2_planes[0]中,不然每一個v4l2_buffer的m枚舉變量是v4l2_plane指針,在querybuf的時候直接把v4l2_planes複製給m中的v4l2_plane指針planes)
  (非plane視頻捕獲設備中planes的個數能理解爲1個?)

  

  ret = call_qop(q, queue_setup, q, &create->format, &num_buffers,&num_planes, q->plane_sizes, q->alloc_ctx);在該函數中調用隊列的queue_setup來設置num_buffers、num_planesplane_sizes

    if (*nbuffers > UVC_MAX_VIDEO_BUFFERS)
      *nbuffers = UVC_MAX_VIDEO_BUFFERS;

    *nplanes = 1;

    sizes[0] = stream->ctrl.dwMaxVideoFrameSize;

    //及每一個buf大小爲一幀數據組成,每一個buf由一個plane組成

 

  ret = __vb2_queue_alloc(q, create->memory, num_buffers,num_planes);

    for (buffer = 0; buffer < num_buffers; ++buffer)

      vb->vb2_queue = q;
      vb->num_planes = num_planes;
      vb->v4l2_buf.index = q->num_buffers + buffer;
      vb->v4l2_buf.type = q->type;
      vb->v4l2_buf.memory = memory;

      ret = __vb2_buf_mem_alloc(vb);

        for (plane = 0; plane < vb->num_planes; ++plane) {
          mem_priv = call_memop(q, alloc, q->alloc_ctx[plane],q->plane_sizes[plane]);

          /* Associate allocator private data with this plane */
          vb->planes[plane].mem_priv = mem_priv;
          vb->v4l2_planes[plane].length = q->plane_sizes[plane];
        }

      q->bufs[q->num_buffers + buffer] = vb;//vb2_queue(q)的vb2_buffer(bufs)裏面保存視頻數據,地址在vb2_buffer的planes.mem_priv

vb2_queue結構體的plane_sizes[0]保存有buf的大小

    


    for (; nbuffers > 0; --nbuffers) {
      mem = vmalloc_32(nbuffers * bufsize);
      if (mem != NULL)
        break;
      }
8. VIDIOC_QUERYBUF
    uvc_query_buffer
      __uvc_query_buffer
      memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf); // 複製參數
9. mmap
  uvc_v4l2_mmap(3.4.2的內核在vb2的planes[plane].mem_priv中指定buf大小,mmap的時候已經將內核數據地址映射到用戶空間,因此數據不用拷貝會去,可是若是視頻數據是壓縮格式,其每一幀的長度不同,在dqbuf的時候須要把buf拷貝回去,裏面有長度等信息)

  (有效數據存在哪裏?
解碼前的和解碼後的,都在vb->planes[plane].mem_priv裏面,這個mem_priv是struct vb2_dc_buf ,裏面記錄了這塊DMA內存的虛擬地址和物理地址,都是在vidioc_reqbufs----vb2_reqbufs----__vb2_queue_alloc(掛到q->bufs)---__vb2_buf_mem_alloc---- call_memop(q, alloc, q->alloc_ctx[plane]---------vb2_dma_contig_alloc的時候記錄好的,經過buf->vaddr = dma_alloc_coherent)

//__find_plane_by_offset函數負責根據用戶空間提供的offset來找到對應的struct vb2_buffer *vb,而後直接調用remap_vmalloc_range函數,將vmalloc空間的內存直接映射到用戶空間中去,用戶空間的地址範圍在(vma->vm_start,vma->vm_end)。

10. VIDIOC_QBUF
  uvc_queue_buffer
    list_add_tail(&buf->stream, &queue->mainqueue);
    list_add_tail(&buf->queue, &queue->irqqueue);

  3.4.2的內核中

  uvc_queue_buffer

    vb2_qbuf

      vb = q->bufs[b->index];//根據index取出vb2_buffer

      __buf_prepare

        uvc_buffer_prepare

      list_add_tail(&vb->queued_entry, &q->queued_list); 

      另一個done_list在urb的傳輸完成函數uvc_video_complete中調用,及有數據的時候纔將buf放入done_list隊列

      uvc_video_complete  //這個函數是在streamOn的時候註冊的回調函數

        stream->decode(urb, stream, buf);//就是uvc_video_decode_isoc(實時端點)/uvc_video_encode_bulk

              uvc_video_decode_isoc

            uvc_video_decode_data//把urb裏面的數據複製到uvc_buffer結構體中的vb2_buffer中的v4l2_buffer中

            uvc_queue_next_buffer//把vb2_buffer根據其內done_entry鏈表固然到vb2_queue的done_list中

              vb2_buffer_done

                list_add_tail(&vb->done_entry, &q->done_list);  

                wake_up(&q->done_wq); 

11. VIDIOC_STREAMON//驅動中,會將以前經過協商設置的參數(圖像格式(yuv,mjpeg,h264等),寬,高,幀率等)經過commit命令(VS_COMMIT_CONTROL )發送到設備,讓其生效,並開始工做。在分配和初始bulk/iso 的urb後(包括設置urb的完成回調函數,回調函數在收到完整urb包的時候會被調用,對於實時端點,一個urb包包含多個iso包),最後調用usb_submit_urb函數,將這個urb提交到usb hcd驅動核心的urb傳輸隊列中。這樣urb就能夠開始收usb控制器接收到的視頻數據料。

/* 1. 向USB攝像頭設置參數: 好比使用哪一個format, 使用這個format下的哪一個frame(分辨率) 
* 參考: uvc_set_video_ctrl / uvc_get_video_ctrl
* 1.1 根據一個結構體uvc_streaming_control設置數據包: 能夠手工uvc_set_video_ctrl 設置,也能夠uvc_get_video_ctrl讀出後再修改
* 1.2 調用usb_control_msg發出數據包

說明:,usb 2.0規範裏面指出,全部的usb設備都會響應主機的一些請求,這些請求必須來自主機,經過設備的默認控制管道發出.(即0號端點所對應的那個管道)這些請求,或者說這些request,是經過控制傳輸來實現的,請求以及請求所需的參數都是經過setup packet來發送的.主機負責創建好這個setup packet.(也就是我們剛纔的那個usb_ctrlrequest,後來的setup_packet.)每一個setup packet包含8個bytes

usb_ctrlrequest分析:

 byte0: bmRequestType

  D7: 控制傳輸數據傳輸的方向,0表示方向爲主機到設備,1表示方向爲設備到主機.(有時控制傳輸除了發命令階段之外,可能還有數據傳輸階段,此處表示的是在數據傳輸那個階段的傳輸方向.)

  D6...5: 請求的類型,0稱爲標準類型(Standard),1稱爲Class,2稱爲Vendor,3則是保留的,不能使用.

  D4...0: 接收者,0表示設備,1表示接口,2表示端點,3表示其它,4...31都是保留的,不能使用

  byte1: bRequest,1個byte,指定了是哪一個請求.每個請求都有一個編號,我們這裏是GET MAX LUN,其編號是FEh.

  byte2...3: wValue,2個bytes,不一樣請求有不一樣的值,我們這裏剛纔已經說了,必須爲0.

  byte4...5: wIndex,2個bytes,不一樣請求有不一樣的值,我們這裏要求被設置爲是interface number.

  byte6...7: wLength,2個bytes,若是接下來有數據傳輸階段,則這個值表徵了數據傳輸階段傳輸多少個bytes

 

控制信號的數據data其實就是uvc_streaming_control結構體

/


  uvc_video_enable(video, 1) // 把所設置的參數發給硬件,而後啓動攝像頭,經過dev的端點0與uvc設備通信
    /* Commit the streaming parameters. */
    uvc_commit_video
      uvc_set_video_ctrl /* 設置格式fromat, frame,這些幀格式都是經過與VS通信設置的,控制信號使用usb_fill_control_urb來填充,其中cmd是usb_ctrlrequest結構體其,只有圖像的亮度等屬性是與VC通信設置,先try,僅設置部分參數,發給USB設備後,其若是支持就會補齊剩下的參數(但在組織urb的時候那個cmd仍是要完整),接着get,最後set */
        ret = __uvc_query_ctrl(video->dev /* 哪個USB設備 */, SET_CUR, 0,
                   video->streaming->intfnum /* 哪個接口: VideoStreaming ,intfnum 的值在probe中被賦值intf->cur_altsetting->desc.bInterfaceNumber,在咱們寫的驅動中,是識別streaming的時候賦值*/,
                  probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,//VS_PROBE_CONTROL 表示嘗試,VS_COMMIT_CONTROL表示設置
                  uvc_timeout_param);

    /* 啓動:Initialize isochronous/bulk URBs and allocate transfer buffers. */
    uvc_init_video(video, GFP_KERNEL);///* d. 設置VideoStreaming Interface所使用的setting(setting值的是接口的設置,一個接口有多個設置)

                        * d.1 從myuvc_params肯定帶寬,myuvc_params是在uvc_commit_video設置參數的時候傳入的結構體裏面dwMaxPayloadTransferSize通常在set以前會try和get,能夠看咱們本身寫的驅動
                        * d.2 根據setting的endpoint能傳輸的wMaxPacketSize
                        * 找到能知足該帶寬的setting

                        代碼是if (intf->num_altsetting > 1){}

                      e、分配設置URB(分配了多個urb_buffer和urb用於接受數據,urb_buffer是一幀的大小,包含多個數據包,即一幀數據是分幾回傳輸的

                      f、提交URB以接收數據
      uvc_init_video_isoc / uvc_init_video_bulk(會初始化接受數據urb,通信端點根據描述符獲取)

        //注意3.4.2的內核complete與下面分析的略有不一樣,見上面qbuf中的分析
        urb->complete = uvc_video_complete; (收到數據後此函數被調用,它又執行下面代碼喚醒在poll中休眠的進程

              buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,queue);//從irqqueue中取出隊列,數據在mainqueue中在取出,數據會保存在buf中,在咱們本身寫的例子中buf的地址等於buf->buf.m.offset + buf->buf.bytesused(這個buf已經被用了多少byte,通常是0)+數據buf的首地址,也就是說數據buf在reqbuf時也分配了,那麼以前的博客中說數據buf在在mmap的時候才分配,在reqbuf的時候只分配了vb2_buf是有問題的,應該是vivi虛擬攝像頭中是這樣作的,但咱們的真實UVC是在reqbuf時分配了數據buf的地址,3.4.2的內核數據不在queue隊列中,而是在done_list中

//3.4.2的內核是stream->decode,其是在uvc_driver.c中的probe函數中的uvc_register_video下的uvc_video_init中設置的

              video->decode(urb, video, buf); ==>uvc_video_decode_isoc(實時端點)/uvc_video_encode_bulk(批量端點) =>uvc_queue_next_buffer => wake_up(&buf->wait);)

                              uvc_video_decode_isoc

                                mem = urb->transfer_buffer + urb->iso_frame_desc[i].offset;//mem是源

                                //下面兩行的代碼是從第七課本身寫uvc驅動中複製過來的,應該在uvc_video_decode_data代碼中處理目的和長度

                                dest = myuvc_queue.mem + buf->buf.m.offset + buf->buf.bytesused;

                                len = urb->iso_frame_desc[i].actual_length;

                                uvc_video_decode_start//對源數據格式進行判斷

                                uvc_video_decode_data//從urb中複製數據到緩存buf中去,即從mem到dest,len應該減去head頭部

              //decode在uvc_video_init中被設置

      usb_submit_urb
12. poll
  uvc_v4l2_poll
    uvc_queue_poll
      poll_wait(file, &buf->wait, wait); // 休眠等待有數據

      3.4.2的內核poll_wait(file, &q->done_wq, wait);// 休眠等待有數據,在uvc_video_complete  的時候wake_up(&q->done_wq)

13. VIDIOC_DQBUF
    uvc_dequeue_buffer
      list_del(&buf->stream);

      3.4.2的內核

      __vb2_get_done_vb

        *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);//vb是vb2_buffer結構體,存放有數據
        list_del(&(*vb)->done_entry);

      list_del(&vb->queued_entry);

14. VIDIOC_STREAMOFF
    uvc_video_enable(video, 0);
        usb_kill_urb(urb);
          usb_free_urb(urb);

分析設置亮度過程:
ioctl: VIDIOC_S_CTRL
    uvc_ctrl_set(設置)
    uvc_ctrl_commit(提交)
      __uvc_ctrl_commit(video, 0);
        uvc_ctrl_commit_entity(video->dev, entity, rollback);
          ret = uvc_query_ctrl(dev /* 哪個USB設備 */, SET_CUR, ctrl->entity->id /* 哪個unit/terminal */,
                    dev->intfnum /* 哪個接口: VideoControl interface */, ctrl->info->selector,
                    uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
                    ctrl->info->size);


總結:
1. UVC設備有2個interface: VideoControl Interface, VideoStreaming Interface
2. VideoControl Interface用於控制,好比設置亮度。它內部有多個Unit/Terminal(在程序裏Unit/Terminal都稱爲entity)
能夠經過相似的函數來訪問:
  ret = uvc_query_ctrl(dev /* 哪個USB設備 */, SET_CUR, ctrl->entity->id /* 哪個unit/terminal */,
      dev->intfnum /* 哪個接口: VC interface */, ctrl->info->selector,
      uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
      ctrl->info->size);
3. VideoStreaming Interface用於得到視頻數據,也能夠用來選擇fromat/frame(VS可能有多種format, 一個format支持多種frame, frame用來表示分辨率等信息)
能夠經過相似的函數來訪問:
  ret = __uvc_query_ctrl(video->dev /* 哪個USB設備 */, SET_CUR, 0,
      video->streaming->intfnum /* 哪個接口: VS */,
      probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
      uvc_timeout_param);
4. 咱們在設置FORMAT時只是簡單的使用video->streaming->format[fmt->index]等數據,
這些數據哪來的?
應是設備被枚舉時設置的,也就是分析它的描述符時設置的。

5. UVC驅動的重點在於:
描述符的分析
屬性的控制: 經過VideoControl Interface來設置
格式的選擇:經過VideoStreaming Interface來設置
數據的得到:經過VideoStreaming Interface的URB來得到

相關文章
相關標籤/搜索