LINUX摄像驱动三:从零开始写虚拟驱动

来源:互联网 发布:校园招聘java面试题库 编辑:程序博客网 时间:2024/05/21 17:32

重大问题1
遇到insmod myvivi.ko不通过
原因是前面的视频没有好好看并且操作,现在补回来

测试虚拟摄像头vivi:
1. 确实ubuntu的内核版本
uname -a
Linux book-desktop 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux
2. 去www.kernel.org下载同版本的内核
https://www.kernel.org/pub/linux/kernel/
解压后把drivers/media/video目录取出
修改它的Makefile为:

KERN_DIR = /usr/src/linux-headers-2.6.31-14-genericall:        make -C $(KERN_DIR) M=`pwd` modulesclean:        make -C $(KERN_DIR) M=`pwd` modules clean        rm -rf modules.orderobj-m   += vivi.oobj-m   += videobuf-core.oobj-m   += videobuf-vmalloc.oobj-m   += v4l2-common.o

3、 make
4、
sudo insmod videobuf-core.ko
sudo insmod videobuf-vmalloc.ko
sudo insmod v4l2-common.ko
发现在这里编译不通过!浪费我一晚上的时间;
解决方法:
sudo modprobe vivi
sudo rmmod vivi
sudo insmod ./vivi.ko
先安装Ubantu里面自带的vivi程序,它会把它所依赖的驱动程序安装进来

sudo rmmod vivi.ko

6、ls /dev/video*
7、xawtv -c /dev/videoX
假如你自己写的myvivi.ko编译出来之后,对应的是video1
那么就使用xawtv -c /dev/video1


video_ioctl2
这是v4l2-ioctl.c提供的默认的标准的ioctl,最终就会调用到 我们在init函数里面所设置的结构体:myvivi_device->ioctl_ops = &myvivi_ioctl_ops;


1、函数里面定时器的问题:
add_timer 在哪里add呢?建议在open函数里面,由于我们写驱动程序的规则,这些资源只有在我们真正用到的时候才会去分配;
先定义该结构体:
static struct timer_list myvivi_timer
在open函数里面:
myvivi_timer.expires = jiffies + 1;
add_timer(&myvivi_timer);
在close函数里面:
del_timer(&myvivi_timer);
在Init函数里面:
/* 用定时器产生数据并唤醒进程 */
init_timer(&myvivi_timer);
myvivi_timer.function = myvivi_timer_function;
然后就是编写function函数,这个function完成了很多工作,显示的主要工作都是在myvivi_fillbuff()这个函数里面完成的!

function函数:
1. 构造数据: 从队列头部取出第1个videobuf, 填充数据
1.1 从本地队列取出第1个videobuf
Garmen:这个函数就是从队列头部取出第一个video_buf
vb = list_entry(myvivi_vb_local_queue.next,struct videobuf_buffer, queue);
然后进行判断
A:该队列头是否为空
B:是否有人在等待这个缓冲区。在done这个位置,估计是判断它有无被设置
如果都不成立 goto out;

1.2 填充数据
vbuf = videobuf_to_vmalloc(vb)
myvivi_fillbuff(vb);
主要是用了myvivi_fillbuf()函数,这个函数需要我们另自编写
Garmen:主要是这个状态要进行设置,不然会出错
vb->state = VIDEOBUF_DONE;

1.3 把videobuf从本地队列中删除
list_del(&vb->queue);

2. 唤醒进程: 唤醒videobuf->done上的进程
wake_up(&vb->done);

3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据:每1/30 秒产生一帧数据
mod_timer(&myvivi_timer, jiffies + HZ/30);


2、函数队列的问题
①:先讲本地队列
构造一个本地队列头:static struct list_head myvivi_vb_local_queue;
在初始化Init函数里面**进行初始化:**INIT_LIST_HEAD(&myvivi_vb_local_queue);

myvivi_buffer_queue函数里 :
把videobuf放入本地一个队列尾部 ,定时器处理函数就可以从本地队列取出videobuf
list_add_tail(&vb->queue, &myvivi_vb_local_queue);

timer_function函数里:
从本地队列取出第1个videobuf

完成填充数据的操作后

A: 把videobuf从本地队列中删除
list_del(&vb->queue);
B: 唤醒进程: 唤醒videobuf->done上的进程
wake_up(&vb->done);

这样一来我们的videobuf就可以拿到本地进行编写填充数据,完了之后再放虎归山了!

②:
队列操作1:进行定义
static struct videobuf_queue myvivi_vb_vidqueue;

队列操作2::初始化
在open函数里面进行初始化:
用videobuf_queue_vmalloc_init函数进行队列

videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,                 NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,                 sizeof(struct videobuf_buffer), NULL);

倒数第2个参数是buffer的头部大小,第四个函数是自旋锁,我们要事先定义它

这个函数的第四个参数是slock:自旋锁
定义:static spinlock_t myvivi_queue_slock;
初始化:在入口函数进行初始化,队列的定义/初始化一个队列(会用到一个spinlock)
spin_lock_init(&myvivi_queue_slock);

这个函数值得深究!
既然是初始化,肯定就要有一系列相关的初始化操作。第二个参数就是对video的初始化操作

包含四个函数

        .buf_setup      = myvivi_buffer_setup, /* 计算大小以免浪费 */        .buf_prepare    = myvivi_buffer_prepare,        .buf_queue      = myvivi_buffer_queue,        .buf_release    = myvivi_buffer_release,

四个函数官方解释为:

  /* 参考documentations/video4linux/v4l2-fram ework.txt: *     drivers\media\video\videobuf-core.c ops->buf_setup   - calculates the size of the video buffers and avoid they            to waste more than some maximum limit of RAM; ops->buf_prepare - fills the video buffer structs and calls            videobuf_iolock() to alloc and prepare mmaped memory; ops->buf_queue   - advices the driver that another buffer were            requested (by read() or by QBUF); ops->buf_release - frees any buffer that were allocated. *

我自己的理解:
Garmen1:buf_setup在videobuf_reqbufs被调用,q->ops->buf_setup(q, &count, &size);
来确定这个conut和size,就是你想分配多少个buf,每个buf多大,通过这个函数重新计算,以免浪费
Garmen2:buf_prepare在videobuf_qbuf中retval = q->ops->buf_prepare(q, buf, field);
Garmen3:通知驱动程序说应用程序正在请求数据
Garmen4:APP不再使用队列时, 用它来释放内存

再值得研究的就是vivi_buffer_prepare函数
里面主要做的事情有:
0、设置videobuf
1、 做些准备工作
2、调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存
注意!在这个vivi.c里面我们用不着,不用这个函数进行分配,这个分配内存的代码段我们是设置不工作的

3、设置状态
vb->state = VIDEOBUF_PREPARED;

还有.buf_queue
/* APP调用ioctl VIDIOC_QBUF时:
* 1、先调用buf_prepare进行一些准备工作
* 2、 把buf放入stream队列
* 3、 调用buf_queue(起通知、记录作用)
*/
里面有两条参数:
vb->state = VIDEOBUF_QUEUED;
把videobuf放入本地一个队列尾部 。定时器处理函数就可以从本地队列取出videobuf
list_add_tail(&vb->queue, &myvivi_vb_local_queue);

队列操作3:注销
在close函数中进行注销:
videobuf_stop(&myvivi_vb_vidqueue);
videobuf_mmap_free(&myvivi_vb_vidqueue);
队列操作4:Poll机制
videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
队列操作5:请求系统分配缓冲区
videobuf_reqbufs(&myvivi_vb_vidqueue, p)
队列操作6:查询所分配的缓冲区
videobuf_querybuf(&myvivi_vb_vidqueue, p)
队列操作7:根据前面分配的缓冲区的信息得出缓冲区大小,进行分配
videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
队列操作8:将缓冲区放入队列
videobuf_qbuf(&myvivi_vb_vidqueue, p)
队列操作9:将缓冲区从队列中取出
videobuf_dqbuf(&myvivi_vb_vidqueue, p, file->f_flags & O_NONBLOCK));
队列操作10:启动
videobuf_streamon(&myvivi_vb_vidqueue)
队列操作11:关闭
videobuf_streamoff(&myvivi_vb_vidqueue);


自己编写的vivi:myvivi.c

/* 仿照vivi.c */#include <linux/module.h>#include <linux/delay.h>#include <linux/errno.h>#include <linux/fs.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/mm.h>#include <linux/ioport.h>#include <linux/init.h>#include <linux/sched.h>#include <linux/pci.h>#include <linux/random.h>#include <linux/version.h>#include <linux/mutex.h>#include <linux/videodev2.h>#include <linux/dma-mapping.h>#include <linux/interrupt.h>#include <linux/kthread.h>#include <linux/highmem.h>#include <linux/freezer.h>#include <media/videobuf-vmalloc.h>#include <media/v4l2-device.h>#include <media/v4l2-ioctl.h>static struct v4l2_format myvivi_format;/* 队列操作1: 定义 */static struct videobuf_queue myvivi_vb_vidqueue;static spinlock_t myvivi_queue_slock;//Garmen:进行本地队列结构体设置static struct list_head myvivi_vb_local_queue;static struct timer_list myvivi_timer;#include "fillbuf.c"/* APP调用ioctl VIDIOC_REQBUFS(申请对流)时会导致此函数被调用, * 它重新调整count和size */static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size){    *size = myvivi_format.fmt.pix.sizeimage;    if (0 == *count)        *count = 32;    return 0;}/* APP调用ioctlVIDIOC_QBUF(入队列)时导致此函数被调用, * 它会填充video_buffer结构体并调用videobuf_iolock来分配内存 * */static int myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,                        enum v4l2_field field){    /* 0. 设置videobuf */    vb->size = myvivi_format.fmt.pix.sizeimage;    vb->bytesperline = myvivi_format.fmt.pix.bytesperline;    vb->width  = myvivi_format.fmt.pix.width;    vb->height = myvivi_format.fmt.pix.height;    vb->field  = field;    /* 1. 做些准备工作 */    //myvivi_precalculate_bars(0);#if 0    /* 2. 调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */    //Garmen:但是在我们这里用不着    if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {        rc = videobuf_iolock(vq, &buf->vb, NULL);        if (rc < 0)            goto fail;    }#endif    /* 3. 设置状态 */    vb->state = VIDEOBUF_PREPARED;    return 0;}/* APP调用ioctl VIDIOC_QBUF时: * 1. 先调用buf_prepare进行一些准备工作 * 2. 把buf放入stream队列 * 3. 调用buf_queue(作用是:起通知、记录作用)   Garmen:问题就来了怎么起通知作用?   答:把videobuf_queue 放入我本地的队列myvivi_vb_local_queue里面、队列尾部      定时器处理函数就可以从本地队列取出这个videobuf   */static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb){    vb->state = VIDEOBUF_QUEUED;    /* 把videobuf放入本地一个队列尾部     * 定时器处理函数就可以从本地队列取出videobuf     */    list_add_tail(&vb->queue, &myvivi_vb_local_queue);}/* APP不再使用队列时, 用它来释放内存 */static void myvivi_buffer_release(struct videobuf_queue *vq,               struct videobuf_buffer *vb){    videobuf_vmalloc_free(vb);    vb->state = VIDEOBUF_NEEDS_INIT;}/* 参考documentations/video4linux/v4l2-fram ework.txt: *     drivers\media\video\videobuf-core.c ops->buf_setup   - calculates the size of the video buffers and avoid they            to waste more than some maximum limit of RAM; ops->buf_prepare - fills the video buffer structs and calls            videobuf_iolock() to alloc and prepare mmaped memory; ops->buf_queue   - advices the driver that another buffer were            requested (by read() or by QBUF); ops->buf_release - frees any buffer that were allocated. * */ //Garmen1:buf_setup在videobuf_reqbufs被调用,q->ops->buf_setup(q, &count, &size); //来确定这个conut和size,就是你想分配多少个buf,每个buf多大,通过这个函数重新计算,以免浪费 //Garmen2:buf_prepare在videobuf_qbuf中retval = q->ops->buf_prepare(q, buf, field);翻到上面有解释 //Garmen3:通知驱动程序说应用程序正在请求数据static struct videobuf_queue_ops myvivi_video_qops = {    .buf_setup      = myvivi_buffer_setup, /* 计算大小以免浪费 */    .buf_prepare    = myvivi_buffer_prepare,    .buf_queue      = myvivi_buffer_queue,    .buf_release    = myvivi_buffer_release,};/* ------------------------------------------------------------------    File operations for the device   ------------------------------------------------------------------*/static int myvivi_open(struct file *file){    /* 队列操作2: 初始化 */     /* 倒数第2个参数是buffer的头部大小 */    //Garmen:第四个函数是自旋锁,我们要事先定义它    videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,            NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,            sizeof(struct videobuf_buffer), NULL);    myvivi_timer.expires = jiffies + 1;    add_timer(&myvivi_timer);    return 0;}static int myvivi_close(struct file *file){    del_timer(&myvivi_timer);    videobuf_stop(&myvivi_vb_vidqueue);    videobuf_mmap_free(&myvivi_vb_vidqueue);    return 0;}static int myvivi_mmap(struct file *file, struct vm_area_struct *vma){    return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);}static unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait){    return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);}static int myvivi_vidioc_querycap(struct file *file, void  *priv,                    struct v4l2_capability *cap){    strcpy(cap->driver, "myvivi");    strcpy(cap->card, "myvivi");    cap->version = 0x0001;    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;    return 0;}/* 列举支持哪种格式 */static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,                    struct v4l2_fmtdesc *f){    //只让他支持一种格式,只支持V4L2_PIX_FMT_YUYV    if (f->index >= 1)        return -EINVAL;    strcpy(f->description, "4:2:2, packed, YUYV");    f->pixelformat = V4L2_PIX_FMT_YUYV;    return 0;}/* 返回当前所使用的格式 *///Garmen:get_fmt的时候,我们将v4l2_format拷贝会应用程序fstatic int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,                    struct v4l2_format *f){    memcpy(f, &myvivi_format, sizeof(struct v4l2_format));    return 0;}/* 测试驱动程序是否支持某种格式 *///Garmen:try_fmt的时候我们做一些判断,然后调整一下宽度高度等,static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,            struct v4l2_format *f){    unsigned int maxw, maxh;    enum v4l2_field field;    if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)        return -EINVAL;    field = f->fmt.pix.field;    if (field == V4L2_FIELD_ANY) {        field = V4L2_FIELD_INTERLACED;    } else if (V4L2_FIELD_INTERLACED != field) {        //dprintk(dev, 1, "Field type invalid.\n");        return -EINVAL;    }    maxw = 1024;    maxh = 768;    /* 调整format的width, height,     * 计算bytesperline, sizeimage     */    v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,                  &f->fmt.pix.height, 32, maxh, 0, 0);    f->fmt.pix.bytesperline =        (f->fmt.pix.width * 16) >> 3;    f->fmt.pix.sizeimage =        f->fmt.pix.height * f->fmt.pix.bytesperline;    return 0;}//Garmen:进行设置static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,                    struct v4l2_format *f){    int ret = myvivi_vidioc_try_fmt_vid_cap(file, priv, f);    if (ret < 0)        return ret;    memcpy(&myvivi_format, f, sizeof(struct v4l2_format));    return ret;}static int myvivi_vidioc_reqbufs(struct file *file, void *priv,              struct v4l2_requestbuffers *p){    return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));}static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p){    return (videobuf_querybuf(&myvivi_vb_vidqueue, p));}static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p){    return (videobuf_qbuf(&myvivi_vb_vidqueue, p));}static int myvivi_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p){    return (videobuf_dqbuf(&myvivi_vb_vidqueue, p,                file->f_flags & O_NONBLOCK));}static int myvivi_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i){    return videobuf_streamon(&myvivi_vb_vidqueue);}static int myvivi_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i){    videobuf_streamoff(&myvivi_vb_vidqueue);    return 0;}static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {        // 表示它是一个摄像头设备        .vidioc_querycap      = myvivi_vidioc_querycap,        /* 用于列举、获得、测试、设置摄像头的数据的格式 */        .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,        .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,        .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,        .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,        /* 缓冲区操作: 申请/查询/放入队列/取出队列 */        .vidioc_reqbufs       = myvivi_vidioc_reqbufs,        .vidioc_querybuf      = myvivi_vidioc_querybuf,        .vidioc_qbuf          = myvivi_vidioc_qbuf,        .vidioc_dqbuf         = myvivi_vidioc_dqbuf,        // 启动/停止        .vidioc_streamon      = myvivi_vidioc_streamon,        .vidioc_streamoff     = myvivi_vidioc_streamoff,   };static const struct v4l2_file_operations myvivi_fops = {    .owner     = THIS_MODULE,    .open    = myvivi_open,    .release = myvivi_close,    .mmap    = myvivi_mmap,    .ioctl   = video_ioctl2,    .poll     = myvivi_poll,};static struct video_device *myvivi_device;static void myvivi_release(struct video_device *vdev){}static void (*function)(unsigned long){    /* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据     */    /* 1.1 从本地队列取出第1个videobuf */}static void myvivi_timer_function(unsigned long data){    struct videobuf_buffer *vb;    void *vbuf;    struct timeval ts;    /* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据     */    /* 1.1 从本地队列取出第1个videobuf */    if (list_empty(&myvivi_vb_local_queue)) {        goto out;    }    //Garmen:这个函数就是从队列头部取出第一个videobuf    vb = list_entry(myvivi_vb_local_queue.next,             struct videobuf_buffer, queue);    /* Garmen:Nobody is waiting on this buffer, return */    if (!waitqueue_active(&vb->done))        goto out;    //如果上面情况都通过,即是都没有goto out    /* 1.2 填充数据 */    vbuf = videobuf_to_vmalloc(vb);    //memset(vbuf, 0xff, vb->size);    myvivi_fillbuff(vb);    vb->field_count++;    do_gettimeofday(&ts);    vb->ts = ts;    //Garmen:主要是这个状态要进行设置    vb->state = VIDEOBUF_DONE;    /* 1.3 把videobuf从本地队列中删除 */    list_del(&vb->queue);    /* 2. 唤醒进程: 唤醒videobuf->done上的进程 */    wake_up(&vb->done);out:    /* 3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据     *    每1/30 秒产生一帧数据     */    mod_timer(&myvivi_timer, jiffies + HZ/30);}static int myvivi_init(void){    int error;    /* 1. 分配一个video_device结构体 */    myvivi_device = video_device_alloc();    /* 2. 设置 */    /* 2.1 */    myvivi_device->release = myvivi_release;    /* 2.2 */    myvivi_device->fops    = &myvivi_fops;    /* 2.3 */    myvivi_device->ioctl_ops = &myvivi_ioctl_ops;    /* 2.4 队列操作     *  a. 定义/初始化一个队列(会用到一个spinlock)     */    spin_lock_init(&myvivi_queue_slock);    /* 用定时器产生数据并唤醒进程 */    init_timer(&myvivi_timer);    myvivi_timer.function = myvivi_timer_function;    //Garmen:入口函数进行初始化    INIT_LIST_HEAD(&myvivi_vb_local_queue);    /* 3. 注册 */    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);    return error;}static void myvivi_exit(void){    video_unregister_device(myvivi_device);    video_device_release(myvivi_device);}module_init(myvivi_init);module_exit(myvivi_exit);MODULE_LICENSE("GPL");