文章目录
1、声明
本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。
韦老师的《驱动大全》:商品详情
其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
2、前言
前面两章,我们基于v4l2在应用层实现了获取摄像头数据,了解了应用层中获取摄像头数据应该有的流程。接着,借着应用层的每一步操作来了解其驱动是如何实现的,从而了解整个v4l2的驱动框架。
从宏观的角度来看,前期的所有准备,无非就是想知道在编写设备驱动程序时要去分配设置哪个结构体,实现哪些关键函数。对于其它子系统的学习也是如此,再者,如今编写驱动的机会只会越来越少,学习驱动的首要目的还是为了养兵千日,用兵一时。
3、虚拟摄像头驱动编写
3.1、编写硬件相关代码
ioctl相关:
1、分配设置注册video_device结构体。
2、实现相关的ioctl函数(如获取设备能力、枚举格式、枚举分辨率、设置分辨率等)。
buffer相关:
3、分配设置vb2_queue结构体。
4、实现相关的vb2_ops函数(如queue_setup()、buf_queue()、start_streaming()、stop_streaming()等函数)。
数据传输:
5、实现start_streaming()、stop_streaming()
3.2、程序示例
python
#include <linux/version.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>
extern unsigned char red[8230];
extern unsigned char blue[8267];
extern unsigned char green[8265];
static int g_copy_cnt = 0;
static struct list_head g_queued_bufs;
static struct timer_list g_virtual_timer;
static struct mutex g_vb_queue_lock; /* Protects vb_queue and capt_file */
static struct mutex g_v4l2_lock; /* Protects everything else */
/* intermediate buffers with raw data from the USB device */
struct virtual_frame_buf {
/* common v4l buffer stuff -- must be first */
struct vb2_v4l2_buffer vb;
struct list_head list;
};
/* Private functions */
static struct virtual_frame_buf *virtual_get_next_buf(void)
{
//unsigned long flags;
struct virtual_frame_buf *buf = NULL;
//spin_lock_irqsave(&s->queued_bufs_lock, flags);
if (list_empty(&g_queued_bufs))
goto leave;
buf = list_entry(g_queued_bufs.next, struct virtual_frame_buf, list);
list_del(&buf->list);
leave:
//spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
return buf;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
static void virtual_timer_expire(unsigned long data)
#else
static void virtual_timer_expire(struct timer_list *t)
#endif
{
/* 从硬件上读到数据(使用red/green/blue数组来模拟) */
/* 获得第1个空闲buffer */
struct virtual_frame_buf *buf = virtual_get_next_buf();
void *ptr;
if (buf)
{
/* 写入buffer */
ptr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
if (g_copy_cnt <= 60)
{
memcpy(ptr, red, sizeof(red));
vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(red));
}
else if(g_copy_cnt <= 120)
{
memcpy(ptr, green, sizeof(green));
vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(green));
}
else
{
memcpy(ptr, blue, sizeof(blue));
vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(blue));
}
/* vb2_buffer_done */
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
}
g_copy_cnt++;
if (g_copy_cnt > 180)
g_copy_cnt = 0;
/* 再次设置timer的超时时间 */
mod_timer(&g_virtual_timer, jiffies + HZ/30);
}
static int virtual_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
strlcpy(cap->driver, "100ask_virtual_video", sizeof(cap->driver));
strlcpy(cap->card, "no-card", sizeof(cap->card));
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE ;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int virtual_enum_fmt_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
if (f->index > 0)
return -EINVAL;
strlcpy(f->description, "100ask motion jpeg", sizeof(f->description));
f->pixelformat = V4L2_PIX_FMT_MJPEG;
return 0;
}
static int virtual_s_fmt_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
/* 分辨用户传入的参数是否可用
* 如果不可用, 给APP提供最接近的、硬件支持的参数
*/
if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG)
return -EINVAL;
f->fmt.pix.width = 800;
f->fmt.pix.height = 600;
return 0;
}
static int virtual_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
if (fsize->index > 0)
return -EINVAL;
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
fsize->discrete.width = 800;
fsize->discrete.height = 600;
return 0;
}
static int virtual_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
struct v4l2_pix_format *pix = &f->fmt.pix;
pix->width = 800;
pix->height = 600;
pix->field = V4L2_FIELD_NONE;
pix->pixelformat = V4L2_PIX_FMT_MJPEG;
pix->bytesperline = 0;
return 0;
}
static const struct v4l2_file_operations virtual_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ioctl_ops virtual_ioctl_ops = {
.vidioc_querycap = virtual_querycap,
.vidioc_enum_fmt_vid_cap = virtual_enum_fmt_cap,
.vidioc_s_fmt_vid_cap = virtual_s_fmt_cap,
.vidioc_enum_framesizes = virtual_enum_framesizes,
.vidioc_g_fmt_vid_cap = virtual_g_fmt,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
};
static struct video_device g_vdev = {
.name = "100ask_virtual_video",
.release = video_device_release_empty,
.fops = &virtual_fops, // v4l2接口层相关的ioctl操作函数
.ioctl_ops = &virtual_ioctl_ops, // 与硬件相关的ioctl操作函数
};
static struct v4l2_device g_v4l2_dev;
static struct vb2_queue g_vb_queue;
static int virtual_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
{
/* 假装:至少需要8个buffer, 每个buffer只有1个plane */
/* Need at least 8 buffers */
if (vq->num_buffers + *nbuffers < 8)
*nbuffers = 8 - vq->num_buffers;
*nplanes = 1;
sizes[0] = PAGE_ALIGN(800*600*2);
return 0;
}
static void virtual_buf_queue(struct vb2_buffer *vb)
{
/* 把这个buffer告诉硬件相关的驱动程序 */
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct virtual_frame_buf *buf =
container_of(vbuf, struct virtual_frame_buf, vb);
//unsigned long flags;
//spin_lock_irqsave(&s->queued_bufs_lock, flags);
list_add_tail(&buf->list, &g_queued_bufs); // 把buffer放到驱动程序里的链表
//spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}
static int virtual_start_streaming(struct vb2_queue *vq, unsigned int count)
{
/* 启动硬件传输 */
/* 使用timer来模拟硬件中断
* 创建timer
* 启动timer
*/
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
setup_timer(&g_virtual_timer, virtual_timer_expire, 0);
#else
timer_setup(&g_virtual_timer, virtual_timer_expire, 0);
#endif
g_virtual_timer.expires = jiffies + HZ/30;
add_timer(&g_virtual_timer);
return 0;
}
static void virtual_stop_streaming(struct vb2_queue *vq)
{
/* 停止硬件传输 */
del_timer(&g_virtual_timer);
while (!list_empty(&g_queued_bufs)) {
struct virtual_frame_buf *buf;
buf = list_entry(g_queued_bufs.next,
struct virtual_frame_buf, list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
}
/* buffer管理要用到的vb2_ops */
static const struct vb2_ops virtul_vb2_ops = {
.queue_setup = virtual_queue_setup, // APP调用ioctl VIDIOC_REQBUFS或VIDIOC_CREATE_BUFS时,驱动程序在分配内存之前,会调用此函数。
.buf_queue = virtual_buf_queue, // 把buffer传送给驱动
.start_streaming = virtual_start_streaming, // 启动硬件传输(操作硬件)
.stop_streaming = virtual_stop_streaming, // 关闭硬件传输(操作硬件)
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
};
static void virtual_video_release(struct v4l2_device *v)
{
}
static int virtual_video_drv_init(void)
{
int ret;
/* 分配/设置/注册video_device */
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 1、设置vb2_queue结构体 */
g_vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
g_vb_queue.drv_priv = NULL;
g_vb_queue.buf_struct_size = sizeof(struct virtual_frame_buf); /* 分配vb时, 分配的空间大小为buf_struct_size */
g_vb_queue.ops = &virtul_vb2_ops; // 设置buffer管理要用到的vb2_ops
g_vb_queue.mem_ops = &vb2_vmalloc_memops; // 设置buffer管理要用到的vb2_mem_ops
g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
ret = vb2_queue_init(&g_vb_queue);
if (ret) {
printk("Could not initialize vb2 queue\n");
return -1;;
}
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
mutex_init(&g_vb_queue_lock);
/* 2、设置video_device结构体 */
g_vdev.queue = &g_vb_queue;
g_vdev.queue->lock = &g_vb_queue_lock;
/* Register the v4l2_device structure(辅助作用) */
g_v4l2_dev.release = virtual_video_release;
strcpy(g_v4l2_dev.name, "virtual_v4l2");
ret = v4l2_device_register(NULL, &g_v4l2_dev);
if (ret) {
printk("Failed to register v4l2-device (%d)\n", ret);
return -1;
}
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
g_vdev.v4l2_dev = &g_v4l2_dev;
g_vdev.lock = &g_v4l2_lock;
mutex_init(&g_v4l2_lock);
/* 3、注册video_device结构体 */
ret = video_register_device(&g_vdev, VFL_TYPE_GRABBER, -1);
if (ret) {
printk("Failed to register as video device (%d)\n", ret);
return -1;
}
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
INIT_LIST_HEAD(&g_queued_bufs);
return 0;
}
static void virtual_video_drv_exit(void)
{
/* 反注册/释放video_device */
v4l2_device_unregister(&g_v4l2_dev);
video_unregister_device(&g_vdev);
}
module_init(virtual_video_drv_init);
module_exit(virtual_video_drv_exit);
MODULE_LICENSE("GPL");