分析的是韦东山第三期视频中的从零编写USB摄像头驱动里的代码
1)入口函数:
里面有什么内容?
根据id_table进行匹配 :表示它能支持哪些设备
当接上能够支持的设备的时候,会调用probe函数
2)在probe函数里注册video_device结构体:
分配video_device结构体:video_device_alloc
设置这个结构体
将v4l2_file_operation结构体的内容赋给video_device的fops
使用.open打开对应的设备节点
使用.ioctl传递相应的参数和命令
使用.mmap把缓存映射到应用空间,让应用可以直接操作这块缓存
使用.poll确定缓存是否有数据
使用.close关闭对应的设备节点
将v4l2_ioctl_ops结构体的内容赋给video_device的ioctl_ops
v4l2_ioctl_ops包含了我们需要操作的ioctl函数
注册这个结构体到内核:video_register_device
3)在disconnect函数里卸载video_device结构体:
卸载video_device结构体:video_unregister_device
释放为video_device申请的内存:video_device_release
4)ioctl填充:
(1)获取这是一个什么设备:
myuvc_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
表示这是一个摄像头设备
具有的性能是图像捕获和流传输
(2)列举这个摄像头设备所能够支持的格式:
myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
这里只支持一种格式:"YUV 4:2:2 (YUYV)"
视频格式包含在drivers/media/video/uvc/uvc_driver.c的uvc_format_desc结构体里面
(3)返回当前所使用的格式:
myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
一开始当前所使用的格式是我们初始化时设置在myuvc_format的格式
所以我们直接将我们初始化好的格式赋给v4l2_format结构体即可:memcpy
(4)测试驱动程序是否支持某种格式,如果支持,强制设置为这种格式:
myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
判断性能格式类型是否图像捕获类型:V4L2_BUF_TYPE_VIDEO_CAPTURE
判断图像格式是否是YUYV:V4L2_PIX_FMT_YUYV
查看支持的分辨率frame是哪几种,强制设置为我们需要的分辨率frame
设置的参数:
f->fmt.pix.width 帧宽
f->fmt.pix.height 帧高
f->fmt.pix.bytesperline 帧宽所占据的字节数
f->fmt.pix.sizeimage 一帧图像所占据的字节数
(5)设置这种格式:
myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
调用myuvc_vidioc_try_fmt_vid_cap函数测试是否支持这种格式
如果支持,强制设置这个格式的分辨率
把强制设置的这个格式的分辨率赋给myuvc_format结构体:memcpy
(6)申请缓存:应用通过这些缓存从驱动程序中获取想要的数据
myuvc_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
申请多少块缓存:nbuffers
申请的缓存大小(页对齐),以整页进行分配:PAGE_ALIGN
至少能够存储一幅图像大小的数据
分配缓存的函数:vmalloc_32
直接分配一整块缓存
如果分配失败,减小块数再进行分配
将分配的缓存全部清零
初始化链表:INIT_LIST_HEAD
&queue->mainqueue
&queue->irqqueue
设置整块缓存中每一块缓存的参数:
第几块缓存:index
偏移地址: offset
每块缓存的大小(真实大小):length
性能格式: type
领域: filed
是否映射: memory
标志: flags
状态: state(刚分配好后的缓存状态为空闲状态:VIDEOBUF_IDLE)
初始化等待队列(缓存无数据是休眠): init_waitqueue_head
缓存起始地址: mem
缓存块数: count
缓存大小(页对齐大小):buf_size
(7)查询缓存状态,如地址信息(用于mmap映射):
myuvc_vidioc_querybuf(struct file *file, void *priv,
struct v4l2_buffer *v4l2_buf)
根据传进来的参数,查找到使用到的缓存,然后复制到应用层
传入的索引值index大于实际具有的缓存数,返回错误
将对应的某个缓存的参数buf结构体拷贝到v4l2_buf结构体:memcpy
设置标志位flags和计算mmap次数:vma_use_count
用来表示缓存是否已经被mmap
计算mmap了多少次
设置被拷贝的缓存的状态state:
VIDEOBUF_DONE :数据处理完成
VIDEOBUF_QUEUED:放入队列,正在队列中进行处理
(8)将缓存mmap到应用空间,供应用使用
myuvc_mmap(struct file *file, struct vm_area_struct *vma)
定义了一个myuvc_buffer的指针*buffer
设置映射的起始地址和结束地址
应用程序调用mmap函数时, 会传入offset参数
根据这个offset在多个缓存中找出指定的缓存
根据虚拟地址找到缓存对应的page构体
把page和APP传入的虚拟地址挂构
把这块page映射到这块虚拟地址上面
计算引用的次数
每映射一次就将引用计数加1
(9)把缓存放入队列,底层的硬件操作函数会把数据放入这个队列的缓存
myuvc_vidioc_qbuf(struct file *file, void *priv,
struct v4l2_buffer *v4l2_buf)
判断应用层传入的v4l2_buf是否有问题:
性能格式是否是图像捕获
是否进行了mmap
索引值是否大于缓存块数
放入队列前,缓存状态是否是空闲状态
修改状态:
将对应索引值的缓存状态修改为入队列状态:VIDEOBUF_QUEUED
将对应索引值的缓存参数状态改为未使用状态:0
放入队列:
队列1:提供给应用层使用
当缓存没有数据时,缓存放入mainqueue队列
当缓冲区有数据时,应用层从mainqueue队列中取出
list_add_tail(链表头,链入的节点mainqueue)
队列头mainqueue需要初始化(open函数里)
队列2:提供产生数据的函数使用
当采集到数据时,从irqqueue队列取出第1个缓存,存入数据
list_add_tail(链表头,链入的节点irqqueue)
队列头irqqueue需要初始化(open函数里)
(10)启动传输
myuvc_vidioc_streamon(struct file *file, void *priv,
enum v4l2_buf_type i)
[1]向摄像头设置参数:使用的格式format;使用的分辨率frame
根据一个结构体uvc_streaming_control设置数据包
调用usb_control_msg发出数据包
测试参数:myuvc_try_streaming_params
应用提供参数结构体,在这个函数里测试可不可用
如果可用,则对这个参数结构体进行补齐
分配一个data结构体
将这个参数结构体赋给data结构体
设置完之后,用usb_control_msg发出usb命令
取出参数:myuvc_get_streaming_params
分配一个data结构体
分配完成后发起usb传输读取数据
数据保存在data结构体
然后通过data结构体赋给uvc_streaming_control结构体
把数据转存到uvc_streaming_control这个结构体里面
设置参数:myuvc_set_streaming_params
根据uvc_version知道发送多少数据
分配好data结构体之后
用上一个函数设置好的uvc_streaming_control来设置data
设置完之后,用usb_control_msg发出usb命令
设置videostreaming interface所使用的setting
从myuvc_params确定带宽
根据setting的endpoint能传输的wMaxPocketSize,找到能够满足该带宽的setting
实现步骤:
首先,得到相应的带宽:bandwidth
轮询所有的setting,找到需要的endpoint
根据endpoint里面的wMaxPocketSize得到一个值
如果这个值比带宽bandwidth大,则选定了这个setting
然后调用usb_set_interface来设置这个setting
[2]分配URB:
实时传输端点一次能传输的最大字节数:psize
一帧数据的最大长度 :size
传一帧数据需要的次数:npackets(向上取整:DIV_ROUND_UP)
分配urb_buffer:usb_buffer_alloc(分配5个)
设置一个全局变量保存urb_buffer的数据(在myuvc_queue结构体里设置)
使用usb_buffer_alloc进行分配
分配后的urb_buffer的物理地址保存在myuvc_queue.urb_dma[i]
分配urb:usb_alloc_urb(分配5个)
使用usb_alloc_urb分配urb
如果上述的分配中任意一次分配出现问题
释放掉所有的urb_buffer:使用usb_buffer_free
将里面所有的urb_bufer设置为NULL
释放掉所有的urb:使用usb_free_urb
将里面所有的urb设置为NULL
[3]设置URB:
对urb结构体里的参数进行设置
urb->dev:指向我们的usb_device结构体
urb->context 不用管,设置为NULL
urb->pipe:设置要传输的端点是哪一个
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->transfer_buffer: 就是我们分配的urb_buffer是哪一个
urb->transfer_dma: 分配的urb_buffer物理地址是多少
urb->complete:当驱动程序接收到一个urb包的时候,产生一个中断,
然后进入这个中断处理函数:
myuvc_video_complete(struct urb *urb)
从irqqueue队列中取出第1个缓冲区,用来存放数据
判断irqqueue队列是否为空,如果不为空
从这个队列里取出第一个缓存myuvc_buffer
源src :urb->transfer_buffer+偏移地址,urb中的数值来源于data
目的dest :我们分配的data结构体
长度length:urb传输的实际大小
判断数据是否有效:根据头部信息来判断
URB数据含义:
data[0]:头部长度
data[1]:错误状态
除去头部以后的数据长度:
urb传输的实际大小 - 头部src[0]的长度
maxlen:缓冲区最多还能存多少数据
nbytes:最多能存的数据和实际要存入的数据比较,取最小值存入
使用memcpy复制数据
判断一帧数据是否已经全部接收到
根据头部信息里的第一个收到的数据里面是否有EOF标志和
已经接收了数据buf.byteuse不等于0判断
除去头部,数据长度等于0,表示它接收到的是一个空的帧
缓存数据修改为:VIDEOBUF_DONE(已经接收完数据)
当接收完一帧数据,从irqqueue中删除这个缓冲区,唤醒等待数据的进程
使用wake_up函数唤醒等待数据的进程 &buf->wait
再次提交urb:usb_submit_urb
urb->number_of_packets:要传输的次数
urb->transfer_buffer_length:总共是多长的数据
设置每次传输的数据保存在哪里
urb->iso_frame_desc[j].offset:偏移地址
urb->iso_frame_desc[j].length:每次传输的大小
[4]提交URB以接受数据
usb_submit_urb
如果提交失败
使用myuvc_uninit_urbs释放urb和urb_buffer
(11)应用调用poll/select来确定缓存是否就绪(有数据)
myuvc_poll(struct file *file, struct poll_table_struct *wait)
从mainqueuq中取出第1个缓冲区
判断它的状态, 如果未就绪, 休眠
如果myuvc_queue.mainqueue队列为空,则发生了错误:list_empty就判断
从list_first_entry函数中取出第一个缓存
它以stream这个节点从myuvc_queue.mainqueue取出缓存myuvc_buffer
然后休眠:poll_wait
(12)应用确定poll/select有数据后,把缓存从队列中取出来
myuvc_vidioc_dqbuf(struct file *file, void *priv,
struct v4l2_buffer *v4l2_buf)
应用发现数据就绪后,从mainqueue里取出第1个缓存buffer
将链入节点mainqueue从队列中删除:list_del(&buf->stream )
(13)停止传输:
myuvc_vidioc_streamoff(struct file *file, void *priv,
enum v4l2_buf_type t)
杀死所有的URB
usb_kill_urb
释放掉URB,并把urb_buffer设置NULL
myuvc_uninit_urbs
设置VideoStreaming Interface为setting 0,让其休眠不工作
usb_set_interface