vivi驅動編寫(一)

vivi 驅動, 即Virtual Video驅動。

基於v4l2驅動框架。

最新版本支持例如

capture(類似攝像頭, 採集, 解碼等) - 最終通過/dev/videoX設備獲得yuv圖像數據。

看協議描述: Video capture devices sample an analog video signal and store the digitized images in memory

存儲模擬視頻信號和數字圖像到內存中的 設備 是Video Capture的設備。


output(輸出, 類似編碼器等) - 通過/dev/videoX設備可以將yuv數據送給驅動。進行處理及輸出編碼結果。

Video output devices encode stills or image sequences as analog video signal. With this interface
applications can control the encoding process and move images from user space to the driver.

Linux 4.xx版本的vivid設備驅動集成了很多的case, 支持capture, output, vbi_catpure, vbi_output等多種類型。

故這裏參考Linux3.16.72版本, 僅完成一個vivi.c, 實現一個video capture類型設備。


簡單邏輯描述:

生成/dev/videoX設備,

用戶可以通過該設備req, enqueue buffers, 等待dequeue buffer並渲染。

vivi驅動需起一個內核線程或定時器, 一定時間對queued buffer填充數據。


ok, 貼一份半成品代碼。 作爲採坑記錄...

以下代碼實現了基本邏輯, 能跑,  但會經常的crash... 有一定線索, 需分析及驗證。


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>
// #include <linux/stacktrace.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/freezer.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("river");

#define MAX_WIDTH 1920
#define MAX_HEIGHT 1200

struct pic_format {
	__u32	width;
	__u32	height;
	__u32	pixelsize;
	__u32	field;
	__u32	fourcc;
	__u32	depth;
};

/**
 * struct vivi_dev - All internal data for one instance of device
 * @v4l2_dev: top-level v4l2 device struct
 * @vdev: video node structure
 * @ctrl_handler: control handler structure
 * @lock: ioctl serialization mutex
 * @std: current SDTV standard
 * @timings: current HDTV timings
 * @format: current pix format
 * @input: current video input (0 = SDTV, 1 = HDTV)
 * @queue: vb2 video capture queue
 * @qlock: spinlock controlling access to buf_list and sequence
 * @buf_list: list of buffers queued for DMA
 * @sequence: frame sequence counter
 */
struct vivi_dev {
	struct v4l2_device v4l2_dev;
	struct video_device vdev;
	struct mutex lock;
	struct pic_format fmt;

	struct vb2_queue queue;

	struct task_struct *kthread;
};

DECLARE_WAIT_QUEUE_HEAD(wait_queue_head);

static void vivi_pdev_release(struct device *dev)
{
	printk(KERN_ALERT "vivi platform device release\n");
}

static struct platform_device vivi_pdev = {
	.name		= "vivi",
	.dev.release	= vivi_pdev_release,
};

/* called from VIDIOC_REQBUFS() and VIDIOC_CREATE_BUFS()
 * prepare buffers,
 * @num_buffers - buffer count
 * @num_planes - plane number
 * @sizes - plane image size
 * @alloc_devs[] -
 */
static int queue_setup(struct vb2_queue *vq,
			unsigned int *num_buffers, unsigned int *num_planes,
			unsigned int sizes[], struct device *alloc_devs[])
{
	unsigned long size;
	struct vivi_dev *dev = vb2_get_drv_priv(vq);

	/* prepare 32 buffers for video queue */
	size = dev->fmt.width * dev->fmt.height * dev->fmt.pixelsize;

	if (*num_buffers == 0)
		*num_buffers = 32;

	*num_planes = 1;
	sizes[0] = size;

	printk(KERN_ALERT "queue_setup, num_planes = %d, sizes[0] = %d\n",
				*num_planes, sizes[0]);

	return 0;
}

/*
 * Prepare the buffer for queueing to the DMA engine: check and set the
 * payload size.
 */
static int buffer_prepare(struct vb2_buffer *vb)
{
	unsigned long size;
	unsigned long plane_size;

	struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);

	size = dev->fmt.width * dev->fmt.height * dev->fmt.pixelsize;

	plane_size = vb2_plane_size(vb, 0);

	printk(KERN_ALERT "plane_size = %d, size = %d\n", plane_size, size);

	if (plane_size < size) {
		printk(KERN_ERR "buffer size too small (%lu < %lu)\n",
		plane_size, size);
		return -EINVAL;
	}

	vb2_set_plane_payload(vb, 0, size);

	return 0;
}

static void buffer_queue(struct vb2_buffer *vb)
{
	/* do nothing, vivi_thread_tick will handle
	 * queued_list dircectly
	 */
	printk(KERN_ALERT "buffer_queue - do nothing\n");

	return;
}

/* fill frame buffer, a circle which will be bigger and bigger... */
static void vivi_fillbuf(struct vivi_dev *dev, struct vb2_buffer *vb)
{
	void *vbuf = NULL;
	int width = dev->fmt.width;
	int height = dev->fmt.height;
	unsigned char (*p)[width][height];
	unsigned int i, j;
	static unsigned int t = 0;

	vbuf = vb2_plane_vaddr(vb, 0);
	p = vbuf;

	memset(p, 0x00, width * height);

#if 0
	for (j = 0; j < height; j++) {
		for (i = 0; i < width; i++) {
			if((j - 240)*(j - 240) + (i - 320)*(i - 320) < (t * t)) {
				*(*(*(p+j)+i)+0) = (unsigned char)0xff;
				*(*(*(p+j)+i)+1) = (unsigned char)0xff;
			} else {
				*(*(*(p+j)+i)+0) = (unsigned char)0;
				*(*(*(p+j)+i)+1) = (unsigned char)0;
			}
		}
	}
#endif

#if 0
	/* green picture */
	for (j = 0; j < height; j++) {
		for (i = 0; i < width; i++) {
			*p[i][j] = (unsigned char)0x00;
		}
	}
#endif

#if 0
	t++;
	printk(KERN_ALERT "%d\n",t);
	if( t >= height/ 2) t = 0;
#endif
}

/* kernel thread wake up and invoke it */
static void vivi_thread_tick(struct vivi_dev *dev)
{
	struct vb2_buffer *buf = NULL;

	printk(KERN_ALERT "vivi_thread_tick...\n");

	if (list_empty(&dev->queue.queued_list)) {
		printk(KERN_ALERT "No active queue to serve\n");
		return;
	}

	/* get the queued_list's first entry */
	buf = list_first_entry(&dev->queue.queued_list,
			struct vb2_buffer, queued_entry);

	printk(KERN_ALERT "fill_vb2_buffer...\n");

	/* fill data */
	vivi_fillbuf(dev, buf);
	printk(KERN_ALERT "filled buffer %p\n", buf->planes[0].mem_priv);

	/* data process done, copy data to done list */
	vb2_buffer_done(buf, VB2_BUF_STATE_DONE);
}

#define WAKE_NUMERATOR 30
#define WAKE_DENOMINATOR 1001
#define BUFFER_TIMEOUT		msecs_to_jiffis(500) /* 0.5 seconds */
#define frames_to_ms(frames)			\
		((frames * WAKE_NUMERATOR * 1000) / WAKE_DENOMINATOR)
static void vivi_routine(struct vivi_dev *dev)
{
	int timeout;

	DECLARE_WAITQUEUE(wait, current);

	add_wait_queue(&wait_queue_head, &wait);
	if (kthread_should_stop())
		goto stop_task;

	printk(KERN_ALERT "vivi_routine\n");
	/* Calculate time to wake up */
	timeout = msecs_to_jiffies(frames_to_ms(1));
	printk(KERN_ALERT "timeout = %d\n", timeout);

	vivi_thread_tick(dev);

	schedule_timeout_interruptible(timeout);

stop_task:
	remove_wait_queue(&wait_queue_head, &wait);
	try_to_freeze();
}

static int vivi_thread(void *data)
{
	struct vivi_dev *dev = (struct vivi_dev *)data;

	set_freezable();
	for (;;) {
		vivi_routine(dev);

		if (kthread_should_stop())
			break;
	}

	printk(KERN_ALERT "thread: exit\n");

	return 0;
}

static int vivi_start_generating(struct vivi_dev *dev)
{
	dev->kthread = kthread_run(vivi_thread, dev, "vivi");

	if (IS_ERR(dev->kthread)) {
		printk(KERN_ERR "kthread_run error\n");
		return PTR_ERR(dev->kthread);
	}

	/* wakes thread */
	wake_up_interruptible(&wait_queue_head);

	return 0;
}

/* start vivi streaming,
 * launch a kthread and fill buffer in queued_list
 * and move it to done_list.
 */
static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
	struct vivi_dev *dev = vb2_get_drv_priv(vq);

	vivi_start_generating(dev);

	printk(KERN_ALERT "start_streaming\n");

	return 0;
}

/* stop vivi streaming,
 * stop kthread.
 */
static void stop_streaming(struct vb2_queue *vq)
{
	struct vivi_dev *dev = vb2_get_drv_priv(vq);

	/* shutdown control thread */
	if (dev->kthread) {
	kthread_stop(dev->kthread);
	dev->kthread = NULL;
	}

	/* no internal dma queue used,
	* so no need to flush any queue here.
	*/

	printk(KERN_ALERT "stop_streaming\n");

	return;
}

/*
 * The vb2 queue ops. Note that since q->lock is set we can use the standard
 * vb2_ops_wait_prepare/finish helper functions. If q->lock would be NULL,
 * then this driver would have to provide these ops.
 */
static const struct vb2_ops vivi_qops = {
	.queue_setup		= queue_setup,
	.buf_prepare		= buffer_prepare,
	.buf_queue		= buffer_queue,
	.start_streaming	= start_streaming,
	.stop_streaming		= stop_streaming,
	.wait_prepare		= vb2_ops_wait_prepare,
	.wait_finish		= vb2_ops_wait_finish,
};

static const struct vb2_queue vivi_queue = {
	.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
	.io_modes = VB2_MMAP,
	.buf_struct_size = sizeof(struct vb2_buffer),
	.ops = &vivi_qops,
	.mem_ops = &vb2_vmalloc_memops,
	.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC,
	.min_buffers_needed = 1,
};

/* query device capability */
static int vidioc_querycap(struct file *file, void *fh,
			struct v4l2_capability *cap)
{
	strlcpy(cap->driver, "vivi", sizeof(cap->driver));
	strlcpy(cap->card, "vivi", sizeof(cap->card));
	snprintf(cap->bus_info, sizeof(cap->bus_info), "vivi");
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING;

	printk(KERN_ALERT "vivi_vidioc_querycap\n");

	// dump_stack();

	return 0;
}

/* enum supported format for the video device */
static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh,
			struct v4l2_fmtdesc *f)
{
	struct vivi_dev *dev = video_drvdata(file);

	if (f->index >= 1)
		return -EINVAL;

	strlcpy(f->description, "vivi", sizeof(f->description));
	f->pixelformat = dev->fmt.fourcc;

	printk(KERN_ALERT "vivi_vidioc_enum_fmt\n");

	return 0;
}

/* re-size format according to vivi device capability */
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
			struct v4l2_format *f)
{
	struct vivi_dev *dev = video_drvdata(file);

	if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
		return -EINVAL;

	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
	v4l_bound_align_image(&f->fmt.pix.width, 48, MAX_WIDTH, 2,
			&f->fmt.pix.height, 32, MAX_HEIGHT, 0, 0);
	f->fmt.pix.bytesperline = (f->fmt.pix.width * dev->fmt.depth) / 8;
	f->fmt.pix.sizeimage = f->fmt.pix.height *f->fmt.pix.bytesperline;

	if (dev->fmt.fourcc == V4L2_PIX_FMT_YUYV)
		f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
	else
		f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;

	return 0;
}

/* set video format */
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
			struct v4l2_format *f)
{
	int ret = 0;
	struct vivi_dev *dev = video_drvdata(file);

	ret = vidioc_try_fmt_vid_cap(file, priv, f);
	if (ret < 0)
		return ret;

	/* save the format setting from user space */
	dev->fmt.fourcc		= V4L2_PIX_FMT_YUYV;
	dev->fmt.pixelsize	= dev->fmt.depth / 8;
	dev->fmt.width		= f->fmt.pix.width;
	dev->fmt.height		= f->fmt.pix.height;
	dev->fmt.field		= f->fmt.pix.field;

	printk(KERN_ALERT "vidioc_s_fmt_vid_cap\n");

	return 0;
}

/* get supported video format */
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
			struct v4l2_format *f) {
	struct vivi_dev *dev = video_drvdata(file);

	/* return format to user space */
	f->fmt.pix.width	= dev->fmt.width;
	f->fmt.pix.height	= dev->fmt.height;
	f->fmt.pix.field	= dev->fmt.field;
	f->fmt.pix.pixelformat	= dev->fmt.fourcc;
	f->fmt.pix.bytesperline = (dev->fmt.width * dev->fmt.depth) / 8;
	f->fmt.pix.sizeimage	= (dev->fmt.height * f->fmt.pix.bytesperline);
	if (dev->fmt.fourcc == V4L2_PIX_FMT_YUYV)
		f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
	else
		f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;

	printk(KERN_ALERT "vidioc_g_fmt_vid_cap\n");

	return 0;
}

static struct v4l2_ioctl_ops vivi_ioctl_ops = {
	.vidioc_querycap	= vidioc_querycap,

	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap	= vidioc_s_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap	= vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap	= vidioc_try_fmt_vid_cap,

	.vidioc_reqbufs		= vb2_ioctl_reqbufs,
	.vidioc_create_bufs	= vb2_ioctl_create_bufs,
	.vidioc_querybuf	= vb2_ioctl_querybuf,
	.vidioc_qbuf		= vb2_ioctl_qbuf,
	.vidioc_dqbuf		= vb2_ioctl_dqbuf,
	.vidioc_expbuf		= vb2_ioctl_expbuf,

	.vidioc_streamon	= vb2_ioctl_streamon,
	.vidioc_streamoff	= vb2_ioctl_streamoff,
};

static const struct v4l2_file_operations vivi_fops = {
	.owner = THIS_MODULE,
	.open = v4l2_fh_open,
	.release = vb2_fop_release,
	.unlocked_ioctl = video_ioctl2,
	.read = vb2_fop_read,
	.mmap = vb2_fop_mmap,
	.poll = vb2_fop_poll,
};

static const struct video_device vivi_template = {
	.name = "vivi",
	.fops = &vivi_fops,
	.ioctl_ops = &vivi_ioctl_ops,
	.release = video_device_release_empty,
	.device_caps = V4L2_CAP_VIDEO_CAPTURE |
		       V4L2_CAP_READWRITE | V4L2_CAP_STREAMING,
};

static int vivi_probe(struct platform_device *pdev)
{
	struct vivi_dev *vivi;
	int ret = 0;
	printk(KERN_ALERT "vivi_probe\n");

	vivi = (struct vivi_dev *)devm_kzalloc(&pdev->dev,
			sizeof(struct vivi_dev), GFP_KERNEL);

	if (!vivi) {
		printk(KERN_ERR "allocate memory for vivi failed\n");
		ret = -ENOMEM;
		goto err;
	}

	/* Initialize the top-level structure */
	ret = v4l2_device_register(&pdev->dev, &vivi->v4l2_dev);
	if (ret < 0) {
		printk(KERN_ERR "v4l2_device regsiter fail, ret(%d)\n", ret);
		goto free_mem;
	}

	/* initialize the vb2 queue */
	mutex_init(&vivi->lock);
	vivi->queue = vivi_queue;
	vivi->queue.drv_priv = vivi;
	vivi->queue.lock = &vivi->lock;
	vivi->queue.dev = &pdev->dev;

	ret = vb2_queue_init(&vivi->queue);
	if (ret)
		goto remove_v4l2;

	/* initialize the video_device structure */
	vivi->vdev = vivi_template;
	vivi->vdev.lock = &vivi->lock;
	vivi->vdev.queue = &vivi->queue;
	vivi->vdev.v4l2_dev = &vivi->v4l2_dev;
	video_set_drvdata(&vivi->vdev, vivi);

	/* default format */
	vivi->fmt.depth = 16;
	vivi->fmt.fourcc = V4L2_PIX_FMT_YUYV;

	/* register video device */
	ret = video_register_device(&vivi->vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0) {
		printk(KERN_ERR "video_device register failed, ret(%d)\n", ret);
		goto remove_v4l2;
	}

	return 0;

remove_v4l2:
	v4l2_device_unregister(&vivi->v4l2_dev);

free_mem:
	kfree(vivi);

err:
	return ret;
}

static int vivi_remove(struct platform_device *pdev)
{
	struct vivi_dev *dev = dev_get_drvdata(&pdev->dev);

	video_unregister_device(&dev->vdev);
	v4l2_device_unregister(&dev->v4l2_dev);

	printk(KERN_ALERT "vivi_remove\n");

	return 0;
}

static struct platform_driver vivi_pdrv = {
	.driver		= {
		.name	= "vivi",
	},
	.probe		= vivi_probe,
	.remove		= vivi_remove,
};

static int __init vivi_init(void)
{
	int ret = 0;

	printk (KERN_ALERT "vivi init succeed\n");

	ret = platform_device_register(&vivi_pdev);
	if (ret < 0) {
		printk(KERN_ERR "vivi platform_device regsiter failed\n");
		goto reg_pdev_fail;
	}

	ret = platform_driver_register(&vivi_pdrv);
	if (ret < 0) {
		printk(KERN_ERR "vivi platform_driver register failed\n");
		goto reg_pdrv_fail;
	}

	return 0;

reg_pdrv_fail:
	platform_device_unregister(&vivi_pdev);
reg_pdev_fail:

	return ret;
}

static void __exit vivi_exit(void)
{
	printk (KERN_ALERT "vivi exit succeed\n");

	platform_driver_unregister(&vivi_pdrv);

	platform_device_unregister(&vivi_pdev);

	return;
}

module_init(vivi_init);
module_exit(vivi_exit);

 

用戶態程序可使用catpure.c等。 網上很多, 暫時不貼了。


ok, 添加關於無規律crash的問題分析。


crash時的log, 通常如下所示:

12428 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300292] vivi_vidioc_querycap                                                                                                                           
12429 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300407] vivi_vidioc_enum_fmt                                                                                                                           
12430 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300428] vidioc_s_fmt_vid_cap                                                                                                                           
12431 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300439] vidioc_g_fmt_vid_cap                                                                                                                           
12432 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300533] queue_setup, num_planes = 1, sizes[0] = 38400                                                                                                  
12433 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300550] vb->vb2_queue = 0000000004a2c8ed                                                                                                               
12434 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300560] vb->memroy = 1                                                                                                                                 
12435 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300615] vb->vb2_queue = 0000000004a2c8ed                                                                                                               
12436 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300625] vb->memroy = 1                                                                                                                                 
12437 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300640] vb->vb2_queue = 0000000004a2c8ed                                                                                                               
12438 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300651] vb->memroy = 1                                                                                                                                 
12439 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300732] __buf_prepare, q = 0000000004a2c8ed                                                                                                            
12440 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300744]  q->memory = 1                                                                                                                                 
12441 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300754] __buf_prepare, q = 0000000004a2c8ed                                                                                                            
12442 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300764]  q->memory = 1                                                                                                                                 
12443 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300774] __buf_prepare, q = 0000000045f93ab6                                                                                                            
12444 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300784]  q->memory = 1668246627                                                                                                                        
12445 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300793] ------------[ cut here ]------------                                                                                                           
12446 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300794] Invalid queue type                                                                                                                             
12447 Dec 28 17:01:55 river-VirtualBox kernel: [ 7976.300853] WARNING: CPU: 1 PID: 2018 at drivers/media/common/videobuf2/videobuf2-core.c:1298 __buf_prepare+0xc6/0x1c0 [videobuf2_common] 

即vb2_queue的內存被踩踏了, q->memory不對了...

對vb2_queue的操作有一些地方。

如用戶線程 queue buffer, dequeue buffer。

以及內核線程 vivi_thread_tick。

故基本上是需要線程間同步的。

但我們驅動模塊只能控制本身的vivi_thread_tick,  故以上三個操作間的同步並不太好實現。


綜上, 並參考linux源碼的vivid.c,

可添加一個dma_queue, 作爲queued_list和done_list之間的橋樑。

每次調用驅動層面buffer_queue時, 將buffer同時存入dma_queue->active 活躍列表中。

而內核線程vivi_thread_tick只從dma_queue->active活躍列表中取出buffer, 並進行數據填充。

如此, queued_list, done_list中的數據就不會和vivi_thread_tick的動作發生衝突。

修改後的代碼請參考下一篇文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章