View Single Post
Old 12-01-2005, 14:34   #17
ilsensine
Senior Member
 
L'Avatar di ilsensine
 
Iscritto dal: Apr 2000
Città: Roma
Messaggi: 15625
Esempio: driver di acquisizione video

Applichiamo quanto visto fin'ora a un caso semi-pratico. L'esempio seguente è un modulo che emula un dispositivo di acquisizione video. La sequenza video generata è prodotta da un codice preso in prestito dal file output_example.c di ffmpeg.

Un driver di acquisizione video è un char device; come fatto con i miscdevice, non gestiremo direttamente il char device, ma ci affideremo al framework v4l già presente nel kernel. Per motivi di semplicità useremo le API v4l v.1, ancora presenti nel kernel.

La novità principale introdotta qui è la ioctl: questo metodo consente di eseguire tutte quelle operazioni che non possono rientrare nel paradigma read/write. Una ioctl ha questo prototipo:
Codice:
int ioctl(struct inode *inode, struct file *file,
	unsigned int cmd, unsigned long arg);
L'ultimo parametro, arg, dipende dal tipo di operazione richiesta in cmd. E' innumerevole fonte di guai, in quanto non è dato sapere a priori cosa c'è dentro: normalmente è un puntatore userspace a una struttura. Inoltre, la differenza nella dimensione del tipo "long" tra i sistemi a 32 e 64 bit completa la frittata. Per accedere al puntatorfe passato in arg, occorrerebbe usare le classiche funzioni di accesso allo spazio utente (copy_*_user & co); fortunatamente, il framework v4l offre un helper (video_usercopy) che fa automaticamente queste noiose operazioni.

Le ioctl v4l sono elencate in include/videodev.h; ne implementeremo solo alcune. Oltre a quelle di inizializzazione e di query, le ioctl principali sono la VIDIOCMCAPTURE e la VIDIOCSYNC. Un driver v4l normalmente mette a disposizione dell'applicazione almeno due buffer di scambio dati, utilizzabili tramite mmap; con la VIDIOCMCAPTURE scheduliamo l'acquisizione su un buffer specificato, con la VIDIOCSYNC attendiamo che il buffer sia pieno. Tra le due l'applicazione può fare altro, l'acquisizione è asincrona. Un programma di acquisizione video normalmente esegue queste operazioni:

- richiede i buffer A e B, e le loro dimensioni
- mmap dei buffer
- VIDIOCMCAPTURE(A)

- VIDIOCMCAPTURE(B)
- VIDIOCSYNC(A)
- visualizza A

- VIDIOCMCAPTURE(A)
- VIDIOCSYNC(B)
- visualizza B

- VIDIOCMCAPTURE(B)
- VIDIOCSYNC(A)
- visualizza A

- VIDIOCMCAPTURE(A)
...

Notate infine che questo modulo utilizza le funzioni esportate dal modulo videodev.ko: occorre caricarlo (modprobe videodev) prima di caricare questo driver. Le immagini prodotte possono essere visualizzate tramite un qualsiasi programma di acquisizione, come xawtv.
Codice:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/videodev.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/mm.h>
#include <asm/atomic.h>

#define VID_HARDWARE_VSAMPLE 255

#define MAX_X 352
#define MAX_Y 288
#define MIN_X 16
#define MIN_Y 16
#define IMAGE_AREA PAGE_ALIGN((MAX_X*MAX_Y*3)/2)

enum istate {
	img_ready,
	img_grabbing,
	img_done
};

struct v_data;
struct v_image {
	unsigned char *map;
	int x;
	int y;
	enum istate state;
	wait_queue_head_t queue;
	struct v_data *vdata;
	spinlock_t lock;
};

struct v_work_data {
	struct work_struct work;
	struct v_image *image;
};

static struct v_data {
	struct video_device *vdev;
	struct video_picture vpict;
	atomic_t frame_index;
	unsigned char *vma;
	struct v_image image[2];
} vdata;



static void v_work_image(void *data)
{
	struct v_work_data *wdata = data;
	struct v_image *image = wdata->image;
	int i, px, py;
	unsigned char *y, *u, *v;

	kfree(wdata);
	WARN_ON(image->state!=img_grabbing);
	y = image->map;
	u = y+(image->x*image->y);
	v = u+(image->x*image->y)/4;
	i = atomic_read(&image->vdata->frame_index);
	atomic_inc(&image->vdata->frame_index);
	for (py=0; py<image->y; py++) {
		for (px=0; px<image->x; px++) {
			y[py*image->x+px] = px + py + i * 3;
		}
	}
	for (py=0; py<image->y/2; py++) {
		for (px=0; px<image->x/2; px++) {
			u[py*image->x/2+px] = 128 + py + i * 2;
			v[py*image->x/2+px] = 64 + px + i * 5;
		}
	}
	smp_wmb();
	image->state = img_done;
	wake_up_interruptible_all(&image->queue);
}

static int v_create_work(struct v_data *vdata, struct video_mmap *map)
{
	struct v_work_data *work_data;
	struct v_image *image;
	if (map->frame<0 || map->frame>1 ||
			map->format!=VIDEO_PALETTE_YUV420P ||
			map->width<MIN_X || map->width>MAX_X ||
			map->height<MIN_Y || map->height>MAX_Y ||
			map->width&1 || map->height&1) {
		return -EINVAL;
	}
	image = vdata->image+map->frame;
	spin_lock(&image->lock);
	if (image->state!=img_ready) {
		spin_unlock(&image->lock);
		return -EBUSY;
	}
	image->state = img_grabbing;
	spin_unlock(&image->lock);
	image->x = map->width;
	image->y = map->height;

	work_data = kmalloc(sizeof(*work_data), GFP_KERNEL);
	if (!work_data)
		return -ENOMEM;
	INIT_WORK(&work_data->work, v_work_image, work_data);
	work_data->image = image;

	schedule_delayed_work(&work_data->work, msecs_to_jiffies(25));
	return 0;
}

static int v_ioctl(struct inode *inode, struct file *file,
	unsigned int cmd, void *arg)
{
	struct video_device *vdev = video_devdata(file);
	struct v_data *vdata = video_get_drvdata(vdev);
	int ret = 0;
	switch (cmd) {
	case VIDIOCGCAP: {
		struct video_capability *vcap = arg;
		memset(vcap, 0, sizeof(*vcap));
		snprintf(vcap->name, sizeof(vcap->name), "v4l_dummy");
		vcap->type = VID_TYPE_CAPTURE | VID_TYPE_SUBCAPTURE;
		vcap->channels = 1;
		vcap->audios = 0;
		vcap->maxwidth = MAX_X;
		vcap->maxheight = MAX_Y;
		vcap->minwidth = MIN_X;
		vcap->minheight = MIN_Y;
		break;
	}
	case VIDIOCGCHAN: {
		struct video_channel *vc = arg;
		if (vc->channel!=0)
			return -EINVAL;
		vc->norm = VIDEO_MODE_PAL;
		vc->type = VIDEO_TYPE_CAMERA;
		vc->flags = 0;
		vc->tuners = 0;
		snprintf(vc->name, sizeof(vc->name),
			"channel %d", vc->channel);
		break;
	}
	case VIDIOCSCHAN: {
		struct video_channel *vc = arg;
		if (vc->channel!=0)
			return -EINVAL;
		if (vc->norm != VIDEO_MODE_PAL &&
				vc->norm != VIDEO_MODE_NTSC &&
				vc->norm != VIDEO_MODE_SECAM &&
				vc->norm != VIDEO_MODE_AUTO)
			return -EINVAL;
		break;
	}
	case VIDIOCGPICT: {
		struct video_picture *pict = arg;
		memcpy(pict, &vdata->vpict, sizeof(*pict));
		break;
	}
	case VIDIOCSPICT: {
		struct video_picture *pict = arg;
		if (pict->palette!=VIDEO_PALETTE_YUV420P ||
				pict->depth!=12)
			return -EINVAL;
		memcpy(&vdata->vpict, pict, sizeof(*pict));
		break;
	}
	case VIDIOCGMBUF: {
		struct video_mbuf *vm = arg;
		memset(vm, 0, sizeof(*vm));
		vm->frames = 2;
		vm->size = vm->frames*IMAGE_AREA;
		vm->offsets[0] = 0;
		vm->offsets[1] = IMAGE_AREA;
		break;
	}
	case VIDIOCGFBUF: {
		struct video_buffer *vb = arg;
		memset(vb, 0, sizeof(*vb));
		break;
	}
	case VIDIOCMCAPTURE: {
		struct video_mmap *map = arg;
		ret = v_create_work(vdata, map);
		break;
	}
	case VIDIOCSYNC: {
		int ipos = *(int *) arg;
		struct v_image *image;
		if (ipos<0 || ipos>1)
			return -EINVAL;
		image = &vdata->image[ipos];
		if (image->state==img_ready)
			return -EINVAL;
		if (wait_event_interruptible(image->queue, image->state==img_done))
			ret = -EINTR;
		else image->state = img_ready;
		break;
	}
	default:
		ret = -ENOIOCTLCMD;
	}
  return ret;
}

static int v_ioctl_if(struct inode *inode, struct file *file,
	unsigned int cmd, unsigned long arg)
{
	return video_usercopy(inode, file, cmd, arg, v_ioctl);
}

static int v_release(struct inode *inode, struct file *file)
{
	struct video_device *vdev = video_devdata(file);
	struct v_data *vdata = video_get_drvdata(vdev);
	flush_scheduled_work();
	vdata->image[0].state = img_ready;
	vdata->image[1].state = img_ready;
	return video_exclusive_release(inode, file);
}

static struct page *v_vm_nopage(struct vm_area_struct *vma,
	unsigned long address, int *type)
{
	unsigned long ofs = (vma->vm_pgoff<<PAGE_SHIFT)+(address-vma->vm_start);
	struct page *ret;
	struct video_device *vdev = vma->vm_private_data;
	struct v_data *vdata =  video_get_drvdata(vdev);

	ofs &= PAGE_MASK;
	if (ofs>=(IMAGE_AREA*2))
		return NOPAGE_SIGBUS;
	ret = vmalloc_to_page(vdata->vma+ofs);
	if (ret)
		get_page(ret);
	if (type)
		*type = VM_FAULT_MINOR;
	return ret;
}

static struct vm_operations_struct v_vm_operations = {
	.nopage = v_vm_nopage,
};

static int v_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct video_device *vdev = video_devdata(file);
	unsigned long size  = vma->vm_end - vma->vm_start;
	unsigned long pg_base = vma->vm_pgoff<<PAGE_SHIFT;

	if ((pg_base+size)>(IMAGE_AREA*2))
		return -EINVAL;
	if (!(vma->vm_flags&VM_SHARED))
		return -EINVAL;
	vma->vm_private_data = vdev;
	vma->vm_flags |= VM_RESERVED;
	vma->vm_ops = &v_vm_operations;
	return 0;
}

static void v_dummy_release(struct video_device *vdev)
{
}

static struct file_operations v_fops = {
	.owner		= THIS_MODULE,
	.open		= video_exclusive_open,
	.release	= v_release,
	.ioctl		= v_ioctl_if,
	.mmap		= v_mmap,
	.llseek		= no_llseek,
};

static struct video_device vdev = {
	.owner =	THIS_MODULE,
	.name =		"v4l_sample",
	.type =		VID_TYPE_CAPTURE,
	.hardware =	VID_HARDWARE_VSAMPLE,
	.fops =		&v_fops,
	.release =	v_dummy_release,
};

static int __init v_init(void)
{
	int ret;
	video_set_drvdata(&vdev, &vdata);
	vdata.vdev = &vdev;
	vdata.vpict.palette = VIDEO_PALETTE_YUV420P;
	vdata.vpict.depth = 12;
	atomic_set(&vdata.frame_index, 0);
	vdata.vma = vmalloc(IMAGE_AREA*2);
	if (!vdata.vma)
		return -ENOMEM;
	vdata.image[0].map = vdata.vma;
	vdata.image[1].map = vdata.image[0].map+IMAGE_AREA;
	vdata.image[0].vdata = &vdata;
	vdata.image[1].vdata = &vdata;
	vdata.image[0].state = img_ready;
	vdata.image[1].state = img_ready;
	init_waitqueue_head(&vdata.image[0].queue);
	init_waitqueue_head(&vdata.image[1].queue);
	spin_lock_init(&vdata.image[0].lock);
	spin_lock_init(&vdata.image[1].lock);
	ret = video_register_device(&vdev, VFL_TYPE_GRABBER, -1);
	if (ret) {
		vfree(vdata.vma);
		return ret;
	}
	printk(KERN_INFO "%s: Using device /dev/video%d\n", __func__, vdev.minor);
	return 0;
}

static void __exit v_exit(void)
{
	video_unregister_device(&vdev);
	vfree(vdata.vma);
}

MODULE_AUTHOR("ilsensine");
MODULE_DESCRIPTION("sample v4l driver");
MODULE_LICENSE("GPL");

module_init(v_init);
module_exit(v_exit);
__________________
0: or %edi, %ecx; adc %eax, (%edx); popf; je 0b-22; pop %ebx; fadds 0x56(%ecx); lds 0x56(%ebx), %esp; mov %al, %al
andeqs pc, r1, #147456; blpl 0xff8dd280; ldrgtb r4, [r6, #-472]; addgt r5, r8, r3, ror #12

Ultima modifica di ilsensine : 23-02-2006 alle 16:05.
ilsensine è offline