从零写一个摄像头驱动
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");
以上是关于从零写一个摄像头驱动的主要内容,如果未能解决你的问题,请参考以下文章