从零写一个摄像头驱动

Posted y4247464

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零写一个摄像头驱动相关的知识,希望对你有一定的参考价值。

1、装载驱动时发现,模块缺少依赖

技术图片

技术图片

解决方法:
  1、先安装Ubantu里面自带的vivi程序,它会把它所依赖的驱动程序安装进来/

  2、sudo rmmod vivi.ko

  3、ls /dev/video*
  4、xawtv -c /dev/video* 
  我写的myvivi.ko编译出来之后,对应的是video1

 

技术图片

/*APP在调用ioctl VIDIOC_QBUF时导致此函数被调用
 *它会填充Video_buffer结构体(头部),并调用videobuf_iolock来分配内存
 *实际上是用mmap函数分配内存的
 */
static int
myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
                        enum v4l2_field field)
{
    /* 1.做些准备工作 */
#if 0
    /* 2.调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */
    if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
        rc = videobuf_iolock(vq, &buf->vb, NULL);
        if (rc < 0)
            goto fail;
    }
#endif

 

队列的内存实际上是调用 fops中的mmap函数分配的,

加入mmap,并实现myvivi_mmap

编译加载后,运行,原来的错误信息消失了

,现在请求buf,分配buf, 队列操作等函数已经实现,现在缺少的是:怎么构造生产数据?

技术图片

再次根据系统提示的信息修改代码

加入摄像头启动/关闭函数

加入v4l2_poll函数(select)来查询数据

技术图片

运行后发现,无法退出界面,是因为 如果poll没有查询到数据则在队列buf->done上休眠,

下一步构造数据唤醒进程。

在vivi.c中,实际上是创建了一个内核线程,线程平时是休眠的。

调用关系如下:

vivi_open
  vivi_start_thread
    vivi_thread /*创建内核线程*/   
     for (;;) {
       vivi_sleep(fh); /* 平时是休眠的 */       
        /* Calculate time to wake up */
         timeout = msecs_to_jiffies(frames_to_ms(1));
         vivi_thread_tick(fh);
            vivi_fillbuff(fh, buf); /*填充buf数据*/
            wake_up(&buf->vb.done); /*填充完后唤醒vb.done*/
         /*会休眠指定时间,时间到了会重新运行,调用vivi_thread_tick*/
         schedule_timeout_interruptible(timeout);

为简单操作,使用定时器唤醒进程

用定时器产生数据并唤醒进程

在入口函数里面初始化timer

 

技术图片
  1 /*仿照vivi.c*/
  2 #include <linux/module.h>
  3 #include <linux/delay.h>
  4 #include <linux/errno.h>
  5 #include <linux/fs.h>
  6 #include <linux/kernel.h>
  7 #include <linux/slab.h>
  8 #include <linux/mm.h>
  9 #include <linux/ioport.h>
 10 #include <linux/init.h>
 11 #include <linux/sched.h>
 12 #include <linux/pci.h>
 13 #include <linux/random.h>
 14 #include <linux/version.h>
 15 #include <linux/mutex.h>
 16 #include <linux/videodev2.h>
 17 #include <linux/dma-mapping.h>
 18 #include <linux/interrupt.h>
 19 #include <linux/kthread.h>
 20 #include <linux/highmem.h>
 21 #include <linux/freezer.h>
 22 #include <media/videobuf-vmalloc.h>
 23 #include <media/v4l2-device.h>
 24 #include <media/v4l2-ioctl.h>
 25 
 26 
 27 static struct v4l2_format myvivi_format; /* 自己定义v4l2_format结构体 */
 28 
 29 /* 队列操作1:  定义*/
 30 static struct videobuf_queue myvivi_vb_vidqueue;
 31 static spinlock_t     myvivi_queue_slock; //定义队列所需的自旋锁,并在入口处初始化
 32 static struct list_head       myvivi_vb_local_queue;  
 33 static struct timer_list myvivi_timer; /*定义一个timer,然后初始化*/
 34 
 35 #include "fillbuf.c"
 36 
 37 /* ------------------------------------------------------------------
 38     Videobuf operations
 39    ------------------------------------------------------------------*/
 40 /*APP在调用ioctl vidioc_reqbufs时会导致此函数被调用
 41  *它重新调整count和size,避免浪费空间
 42  */
 43 static int
 44 myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
 45 {
 46 
 47     *size = myvivi_format.fmt.pix.sizeimage;
 48 
 49     if (0 == *count)
 50         *count = 32;
 51 
 52     return 0;
 53 }
 54 
 55 /*APP在调用ioctl VIDIOC_QBUF时导致此函数被调用
 56  *它会填充Video_buffer结构体(头部),并调用videobuf_iolock来分配内存
 57  *实际上是用mmap函数分配内存的
 58  */
 59 static int
 60 myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
 61                         enum v4l2_field field)
 62 {
 63     /* 0.设置videobuf */
 64     vb->size = myvivi_format.fmt.pix.pixelformat;
 65     vb->bytesperline = myvivi_format.fmt.pix.pixelformat;
 66     vb->width  = fh->width;
 67     vb->height = fh->height;
 68     vb->field  = field;
 69 
 70     /* 1.做些准备工作 */
 71     myvivi_precalculate_bars(0); //第一个通道的条纹
 72 #if 0
 73     /* 2.调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */
 74     if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
 75         rc = videobuf_iolock(vq, &buf->vb, NULL);
 76         if (rc < 0)
 77             goto fail;
 78     }
 79 #endif
 80     /* 3.设置状态,填充vb结构体,说明状态已经准备好 */
 81     vb->state = VIDEOBUF_PREPARED;
 82 
 83     return 0;
 84 }
 85 
 86 /*APP在调用ioctl VIDIOC_QBUF时
 87  *1.先调用buffer_prepare进行准备工作
 88  *2.把buf放入队列
 89  *3.调用buf_queue(起通知、记录作用)
 90  */
 91 static void
 92 myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
 93 {
 94     vb->state = VIDEOBUF_QUEUED;
 95     
 96     /*把videobuf放入本地一个队列尾部
 97      *定时时间到后,定时器处理函数就可以从本地队列取出videobuf
 98      */
 99     list_add_tail(&vb->queue, &myvivi_vb_local_queue);
100 }
101 
102 /*APP不再使用队列时,使用此函数释放内存
103  */
104 static void myvivi_buffer_release(struct videobuf_queue *vq,
105                struct videobuf_buffer *vb)
106 {
107     videobuf_vmalloc_free(vb);
108     vb->state = VIDEOBUF_NEEDS_INIT; //状态改为初始状态
109 }
110 
111 
112 /* 在内核文档的v4l2-framework中查找
113  * buf_setup /buf_prepare/buf_queue/buf_release
114  *以上四个函数设置好后,videobuf_queue_ops结构体就设置好了
115  */
116 static struct videobuf_queue_ops myvivi_video_qops = {
117     .buf_setup      = myvivi_buffer_setup,
118     .buf_prepare    = myvivi_buffer_prepare,
119     .buf_queue      = myvivi_buffer_queue,
120     .buf_release    = myvivi_buffer_release,
121 };
122 
123 
124 /* ------------------------------------------------------------------
125     File operations for the device
126    ------------------------------------------------------------------*/
127 
128 static int myvivi_open(struct file *file)
129 {
130     /* 队列操作2: 
131      *初始化
132      *  1).    定义队列:myvivi_vb_vidqueue            
133      *  2). 设置队列操作函数结构体,并实现里面的操作函数:myvivi_video_qops
134      *  3). 定义队列所需的自旋锁,并在入口处初始化
135      *  4). 设置buffer头部大小
136      * ---------设置好队列相关初始化后,继续完成有关于队列与缓冲区的ioctl操作----------
137      */
138     videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,
139             NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,
140             sizeof(struct videobuf_buffer), NULL);  /* 倒数第二个参数是buffer头部大小 */
141 
142     myvivi_timer.expires = jiffies + 1; /*expires为定时器到时时间,*/
143     add_timer(&myvivi_timer); /*在用到vivitimer时才去分配,故放在open,非入口函数*/
144 
145     return 0;
146 }
147 
148 
149 static int myvivi_close(struct file *file)
150 {
151     videobuf_stop(&myvivi_vb_vidqueue);
152     videobuf_mmap_free(&myvivi_vb_vidqueue);
153     del_timer(&myvivi_timer);
154     return 0;
155 }
156 
157 static int myvivi_mmap(struct file *file, struct vm_area_struct *vma)
158 {
159     return  videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
160     
161 }
162 
163 static unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait)
164 {
165     return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
166 }
167 
168 
169 /*------------------------------------------------------------------
170  */
171 
172 static int myvivi_vidioc_querycap(struct file *file, void  *priv,
173                     struct v4l2_capability *cap)
174 {
175 
176     strcpy(cap->driver, "myvivi");
177     strcpy(cap->card, "myvivi");
178     cap->version = 0x0001;
179     cap->capabilities =    V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
180     return 0;
181 }
182 
183 
184 /* ------------------------------------------------------------------
185     用于列举、获得、测试、设置摄像头的数据的格式
186    ------------------------------------------------------------------*/
187 
188 /* 列举支持哪种格式,此处只让其支持一种格式 */
189 static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
190                     struct v4l2_fmtdesc *f)
191 {
192 
193     if (f->index >= 1) /* index为格式的种数,在此判断条件设置为1 */
194         return -EINVAL;
195 
196     strcpy(f->description, "4:2:2, packed, YUYV");
197     f->pixelformat = V4L2_PIX_FMT_YUYV; //s设置支持一种格式
198     return 0;
199 }
200 
201 /* 返回当前所使用的格式 */
202 static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
203                     struct v4l2_format *f)
204 {
205     memcpy(f, &myvivi_format, sizeof(myvivi_format));
206     return (0);
207 }
208 
209 /* 测试驱动程序是否支持某种格式,此处即上面固定的格式 */
210 static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
211             struct v4l2_format *f)
212 {
213     unsigned int maxw, maxh;
214     enum v4l2_field field;
215     
216     /* 判断是否是支持的格式,否则返回错误 */
217     if(f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
218     return -EINVAL;
219     
220     field = f->fmt.pix.field;
221 
222     if (field == V4L2_FIELD_ANY) {
223         field = V4L2_FIELD_INTERLACED;
224     } else if (V4L2_FIELD_INTERLACED != field) {
225         return -EINVAL;
226     }
227 
228     maxw  = 1024;
229     maxh  = 768;
230 
231     /* 调整format的width、height,
232      * 计算每一行的字节数bytesperline、sizeimage
233      */
234     v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
235                   &f->fmt.pix.height, 32, maxh, 0, 0);
236     f->fmt.pix.bytesperline =
237         (f->fmt.pix.width * 16) >> 3;
238     f->fmt.pix.sizeimage =
239         f->fmt.pix.height * f->fmt.pix.bytesperline;
240 
241     return 0;
242 }
243 
244 
245 static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
246                     struct v4l2_format *f)
247 {
248     /* 判断 */
249     int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
250     if (ret < 0)
251         return ret;
252     /* 把传进来的参数复制到myvivi_format */
253     memcpy(&myvivi_format, f, sizeof(myvivi_format));
254 
255     return ret;
256 }
257 
258 
259 /* ------------------------------------------------------------------
260      缓冲区操作: 申请/查询/放入队列/取出队列
261    ------------------------------------------------------------------*/
262 
263 static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
264               struct v4l2_requestbuffers *p)
265 {
266     return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
267 }
268 
269 static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
270 {
271     return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
272 }
273 
274 static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
275 {
276     return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
277 }
278 
279 static int myvivi_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
280 {
281     return (videobuf_dqbuf(&myvivi_vb_vidqueue, p,
282                 file->f_flags & O_NONBLOCK));
283 }
284 
285 
286 /* ------------------------------------------------------------------
287                              启动/停止摄像头
288    ------------------------------------------------------------------*/
289 
290 static int myvivi_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
291 {
292     return videobuf_streamon(&myvivi_vb_vidqueue);
293 }
294 
295 static int myvivi_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
296 {
297     return videobuf_streamoff(&myvivi_vb_vidqueue);
298 }
299 
300 
301 static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
302 
303     //摄像头驱动程序必需的11个ioctl:
304     // 表示它是一个摄像头设备
305     .vidioc_querycap      = myvivi_vidioc_querycap,
306 
307     /* 用于列举、获得、测试、设置摄像头的数据的格式 */
308     .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
309     .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
310     .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
311     .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
312 
313 
314     /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
315     .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
316     .vidioc_querybuf      = myvivi_vidioc_querybuf,
317     .vidioc_qbuf          = myvivi_vidioc_qbuf,
318     .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
319 
320     /* 启动/停止 */
321     .vidioc_streamon      = myvivi_vidioc_streamon,
322     .vidioc_streamoff     = myvivi_vidioc_streamoff,    
323 
324 };
325 
326 static const struct v4l2_file_operations myvivi_fops = {
327     .owner     = THIS_MODULE,
328     .open      = myvivi_open,
329     .release   = myvivi_close,
330     .mmap      = myvivi_mmap,
331     .ioctl     = video_ioctl2, /* V4L2 ioctl handler 最终会调用到ioctl_ops里面的ioctl*/   
332     .poll      = myvivi_poll,
333     
334 };
335 
336 
337 /*  video_device结构体用于在/dev目录下生成设备节点文件,把操作设备的接口暴露给用户空间 */
338 static struct video_device *myvivi_device;
339 
340 static void myvivi_release(struct video_device *vdev)
341 {
342 
343 }
344 
345 static void myvivi_timer_function(unsigned long data)
346 {
347     struct videobuf_buffer *vb;
348     void *vbuf;
349     struct timeval ts;
350     
351     /* 1. 构造数据:从队列头部取出第一个videobuf,填充数据*/
352     
353         /* 如果链表是空的,表明还没有应用程序关心里面的数据->跳出 */
354      if (list_empty(&myvivi_vb_local_queue)) {
355         goto out; //
356     }
357         /* 1.1 从队列myvivi_vb_local_queue的头部取出第一个videobuf*/
358     vb = list_entry(myvivi_vb_local_queue.next,
359              struct videobuf_buffer, queue);
360 
361         /* 本地队列中没有队列等待唤醒,跳出 */
362     if (!waitqueue_active(&vb->done))
363         goto out;
364 
365         /* 1.2 填充数据 */
366         vbuf = videobuf_to_vmalloc(vb);
367         //memset(vbuf, 0, vb->size); //为新分配的vbuf内存填充数据(为申请的内存做初始化工作)
368         myvivi_fillbuff(vb);
369         
370         /* 填充后要将状态标志为DONE,状态会在poll函数中进行判断 */
371         vb->field_count++;
372         do_gettimeofday(&ts);
373         vb->ts = ts;
374         vb->state = VIDEOBUF_DONE;
375         
376         /* 1.3 把videobuf从本地队列中删除 */
377         list_del(&vb->queue);
378         
379     /* 2. 唤醒进程: 唤醒videobuf->done上的进程 */
380     wake_up(&vb->done);
381     
382 out:
383     /* 3. 修改timer的超时时间:30fps, 1秒内有30帧
384      *   每1/30秒时间产生一帧数据
385      */
386      mod_timer(&myvivi_timer, jiffies + HZ/30);
387 }
388 
389 /*对于驱动程序,先写入口、出口函数*/
390 static int myvivi_init(void)
391 {
392     int error;
393     
394     /* 1.分配一个video_device结构体*/
395     myvivi_device = video_device_alloc();  //
396 
397     /* 2.设置结构体 */
398 
399     /* 2.1 */
400     myvivi_device->release = myvivi_release; //结构体中要有release函数,否则返回错误
401 
402     /* 2.2 */
403     myvivi_device->fops    = &myvivi_fops;
404 
405     /* 2.3 */
406     myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
407 
408     /* 2.4 队列操作
409      *  a. 定义/初始化一个队列(会用到一个spinlock)
410      */
411     spin_lock_init(&myvivi_queue_slock); //初始化锁
412     /* 3.注册 */
413     /* nr: device node number */
414     error = video_register_device(myvivi_device,VFL_TYPE_GRABBER, -1);
415 
416     /*用定时器产生数据并唤醒进程*/
417     init_timer(&myvivi_timer); //初始化timer
418     myvivi_timer.function = myvivi_timer_function; /* 超时处理函数 */
419 
420     INIT_LIST_HEAD(&myvivi_vb_local_queue); //初始化本地队列
421     
422     return error;
423 }
424 
425 static void myvivi_exit(void)
426 {
427     video_unregister_device(myvivi_device);
428     video_device_release(myvivi_device);
429 }
430 
431 /*修饰入口出口函数,使用insmod,rmmod时就会调用对应函数*/
432 module_init(myvivi_init);
433 module_exit(myvivi_exit);
434 
435 /*加入GPL协议,通用性公开许可证(General Public License)*/
436 MODULE_LICENSE("GPL");
myvivi.c

 

以上是关于从零写一个摄像头驱动的主要内容,如果未能解决你的问题,请参考以下文章

V4L2学习流程

手把手教你从零写一个简单的 VUE

手把手教你从零写一个简单的 VUE

从零写一个编译器:语法分析之表驱动语法分析

理解ALSA:从零写ASoC驱动

28从零写UVC驱动之实现设置属性