1
linux/drivers/media/video/vivi.c
Mauro Carvalho Chehab 35d6270b80 V4L/DVB (5020): Fix: disable interrupts while at KM_BOUNCE_READ
vivi.c uses the KM_BOUNCE_READ with local interrupts enabled. 
This means that if a disk interrupt occurs while vivi.c is using this
fixmap slot, the vivi.c driver will, upon return from that interrupt, find
that the fixmap slot now points at a different physical page.
The net result will probably be rare corruption of disk file contents,
because viv.c will now be altering the page which the disk code was
recently using. 

Thanks to Andrew Morton for pointing this.
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
2007-01-15 16:25:28 -02:00

1397 lines
33 KiB
C

/*
* Virtual Video driver - This code emulates a real video device with v4l2 api
*
* Copyright (c) 2006 by:
* Mauro Carvalho Chehab <mchehab--a.t--infradead.org>
* Ted Walther <ted--a.t--enumera.com>
* John Sokol <sokol--a.t--videotechnology.com>
* http://v4l.videotechnology.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD Licence, GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version
*/
#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/videodev2.h>
#include <linux/dma-mapping.h>
#ifdef CONFIG_VIDEO_V4L1_COMPAT
/* Include V4L1 specific functions. Should be removed soon */
#include <linux/videodev.h>
#endif
#include <linux/interrupt.h>
#include <media/video-buf.h>
#include <media/v4l2-common.h>
#include <linux/kthread.h>
#include <linux/highmem.h>
#include <linux/freezer.h>
/* Wake up at about 30 fps */
#define WAKE_NUMERATOR 30
#define WAKE_DENOMINATOR 1001
#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */
/* These timers are for 1 fps - used only for testing */
//#define WAKE_DENOMINATOR 30 /* hack for testing purposes */
//#define BUFFER_TIMEOUT msecs_to_jiffies(5000) /* 5 seconds */
#include "font.h"
#define VIVI_MAJOR_VERSION 0
#define VIVI_MINOR_VERSION 4
#define VIVI_RELEASE 0
#define VIVI_VERSION KERNEL_VERSION(VIVI_MAJOR_VERSION, VIVI_MINOR_VERSION, VIVI_RELEASE)
/* Declare static vars that will be used as parameters */
static unsigned int vid_limit = 16; /* Video memory limit, in Mb */
static struct video_device vivi; /* Video device */
static int video_nr = -1; /* /dev/videoN, -1 for autodetect */
/* supported controls */
static struct v4l2_queryctrl vivi_qctrl[] = {
{
.id = V4L2_CID_AUDIO_VOLUME,
.name = "Volume",
.minimum = 0,
.maximum = 65535,
.step = 65535/100,
.default_value = 65535,
.flags = 0,
.type = V4L2_CTRL_TYPE_INTEGER,
},{
.id = V4L2_CID_BRIGHTNESS,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Brightness",
.minimum = 0,
.maximum = 255,
.step = 1,
.default_value = 127,
.flags = 0,
}, {
.id = V4L2_CID_CONTRAST,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Contrast",
.minimum = 0,
.maximum = 255,
.step = 0x1,
.default_value = 0x10,
.flags = 0,
}, {
.id = V4L2_CID_SATURATION,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Saturation",
.minimum = 0,
.maximum = 255,
.step = 0x1,
.default_value = 127,
.flags = 0,
}, {
.id = V4L2_CID_HUE,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Hue",
.minimum = -128,
.maximum = 127,
.step = 0x1,
.default_value = 0,
.flags = 0,
}
};
static int qctl_regs[ARRAY_SIZE(vivi_qctrl)];
#define dprintk(level,fmt, arg...) \
do { \
if (vivi.debug >= (level)) \
printk(KERN_DEBUG "vivi: " fmt , ## arg); \
} while (0)
/* ------------------------------------------------------------------
Basic structures
------------------------------------------------------------------*/
struct vivi_fmt {
char *name;
u32 fourcc; /* v4l2 format id */
int depth;
};
static struct vivi_fmt format = {
.name = "4:2:2, packed, YUYV",
.fourcc = V4L2_PIX_FMT_YUYV,
.depth = 16,
};
struct sg_to_addr {
int pos;
struct scatterlist *sg;
};
/* buffer for one video frame */
struct vivi_buffer {
/* common v4l buffer stuff -- must be first */
struct videobuf_buffer vb;
struct vivi_fmt *fmt;
struct sg_to_addr *to_addr;
};
struct vivi_dmaqueue {
struct list_head active;
struct list_head queued;
struct timer_list timeout;
/* thread for generating video stream*/
struct task_struct *kthread;
wait_queue_head_t wq;
/* Counters to control fps rate */
int frame;
int ini_jiffies;
};
static LIST_HEAD(vivi_devlist);
struct vivi_dev {
struct list_head vivi_devlist;
struct semaphore lock;
int users;
/* various device info */
unsigned int resources;
struct video_device vfd;
struct vivi_dmaqueue vidq;
/* Several counters */
int h,m,s,us,jiffies;
char timestr[13];
};
struct vivi_fh {
struct vivi_dev *dev;
/* video capture */
struct vivi_fmt *fmt;
unsigned int width,height;
struct videobuf_queue vb_vidq;
enum v4l2_buf_type type;
};
/* ------------------------------------------------------------------
DMA and thread functions
------------------------------------------------------------------*/
/* Bars and Colors should match positions */
enum colors {
WHITE,
AMBAR,
CYAN,
GREEN,
MAGENTA,
RED,
BLUE
};
static u8 bars[8][3] = {
/* R G B */
{204,204,204}, /* white */
{208,208, 0}, /* ambar */
{ 0,206,206}, /* cyan */
{ 0,239, 0}, /* green */
{239, 0,239}, /* magenta */
{205, 0, 0}, /* red */
{ 0, 0,255}, /* blue */
{ 0, 0, 0}
};
#define TO_Y(r,g,b) (((16829*r +33039*g +6416*b + 32768)>>16)+16)
/* RGB to V(Cr) Color transform */
#define TO_V(r,g,b) (((28784*r -24103*g -4681*b + 32768)>>16)+128)
/* RGB to U(Cb) Color transform */
#define TO_U(r,g,b) (((-9714*r -19070*g +28784*b + 32768)>>16)+128)
#define TSTAMP_MIN_Y 24
#define TSTAMP_MAX_Y TSTAMP_MIN_Y+15
#define TSTAMP_MIN_X 64
static void prep_to_addr(struct sg_to_addr to_addr[],
struct videobuf_buffer *vb)
{
int i, pos=0;
for (i=0;i<vb->dma.nr_pages;i++) {
to_addr[i].sg=&vb->dma.sglist[i];
to_addr[i].pos=pos;
pos += vb->dma.sglist[i].length;
}
}
static int get_addr_pos(int pos, int pages, struct sg_to_addr to_addr[])
{
int p1=0,p2=pages-1,p3=pages/2;
/* Sanity test */
BUG_ON (pos>=to_addr[p2].pos+to_addr[p2].sg->length);
while (p1+1<p2) {
if (pos < to_addr[p3].pos) {
p2=p3;
} else {
p1=p3;
}
p3=(p1+p2)/2;
}
if (pos >= to_addr[p2].pos)
p1=p2;
return (p1);
}
static void gen_line(struct sg_to_addr to_addr[],int inipos,int pages,int wmax,
int hmax, int line, char *timestr)
{
int w,i,j,pos=inipos,pgpos,oldpg,y;
char *p,*s,*basep;
struct page *pg;
u8 chr,r,g,b,color;
unsigned long flags;
spinlock_t spinlock;
spin_lock_init(&spinlock);
/* Get first addr pointed to pixel position */
oldpg=get_addr_pos(pos,pages,to_addr);
pg=pfn_to_page(sg_dma_address(to_addr[oldpg].sg) >> PAGE_SHIFT);
spin_lock_irqsave(&spinlock,flags);
basep = kmap_atomic(pg, KM_BOUNCE_READ)+to_addr[oldpg].sg->offset;
/* We will just duplicate the second pixel at the packet */
wmax/=2;
/* Generate a standard color bar pattern */
for (w=0;w<wmax;w++) {
r=bars[w*7/wmax][0];
g=bars[w*7/wmax][1];
b=bars[w*7/wmax][2];
for (color=0;color<4;color++) {
pgpos=get_addr_pos(pos,pages,to_addr);
if (pgpos!=oldpg) {
pg=pfn_to_page(sg_dma_address(to_addr[pgpos].sg) >> PAGE_SHIFT);
kunmap_atomic(basep, KM_BOUNCE_READ);
basep= kmap_atomic(pg, KM_BOUNCE_READ)+to_addr[pgpos].sg->offset;
oldpg=pgpos;
}
p=basep+pos-to_addr[pgpos].pos;
switch (color) {
case 0:
case 2:
*p=TO_Y(r,g,b); /* Luminance */
break;
case 1:
*p=TO_U(r,g,b); /* Cb */
break;
case 3:
*p=TO_V(r,g,b); /* Cr */
break;
}
pos++;
}
}
/* Checks if it is possible to show timestamp */
if (TSTAMP_MAX_Y>=hmax)
goto end;
if (TSTAMP_MIN_X+strlen(timestr)>=wmax)
goto end;
/* Print stream time */
if (line>=TSTAMP_MIN_Y && line<=TSTAMP_MAX_Y) {
j=TSTAMP_MIN_X;
for (s=timestr;*s;s++) {
chr=rom8x16_bits[(*s-0x30)*16+line-TSTAMP_MIN_Y];
for (i=0;i<7;i++) {
if (chr&1<<(7-i)) { /* Font color*/
r=bars[BLUE][0];
g=bars[BLUE][1];
b=bars[BLUE][2];
r=g=b=0;
g=198;
} else { /* Background color */
r=bars[WHITE][0];
g=bars[WHITE][1];
b=bars[WHITE][2];
r=g=b=0;
}
pos=inipos+j*2;
for (color=0;color<4;color++) {
pgpos=get_addr_pos(pos,pages,to_addr);
if (pgpos!=oldpg) {
pg=pfn_to_page(sg_dma_address(
to_addr[pgpos].sg)
>> PAGE_SHIFT);
kunmap_atomic(basep,
KM_BOUNCE_READ);
basep= kmap_atomic(pg,
KM_BOUNCE_READ)+
to_addr[pgpos].sg->offset;
oldpg=pgpos;
}
p=basep+pos-to_addr[pgpos].pos;
y=TO_Y(r,g,b);
switch (color) {
case 0:
case 2:
*p=TO_Y(r,g,b); /* Luminance */
break;
case 1:
*p=TO_U(r,g,b); /* Cb */
break;
case 3:
*p=TO_V(r,g,b); /* Cr */
break;
}
pos++;
}
j++;
}
}
}
end:
kunmap_atomic(basep, KM_BOUNCE_READ);
spin_unlock_irqrestore(&spinlock,flags);
}
static void vivi_fillbuff(struct vivi_dev *dev,struct vivi_buffer *buf)
{
int h,pos=0;
int hmax = buf->vb.height;
int wmax = buf->vb.width;
struct videobuf_buffer *vb=&buf->vb;
struct sg_to_addr *to_addr=buf->to_addr;
struct timeval ts;
/* Test if DMA mapping is ready */
if (!sg_dma_address(&vb->dma.sglist[0]))
return;
prep_to_addr(to_addr,vb);
/* Check if there is enough memory */
BUG_ON(buf->vb.dma.nr_pages << PAGE_SHIFT < (buf->vb.width*buf->vb.height)*2);
for (h=0;h<hmax;h++) {
gen_line(to_addr,pos,vb->dma.nr_pages,wmax,hmax,h,dev->timestr);
pos += wmax*2;
}
/* Updates stream time */
dev->us+=jiffies_to_usecs(jiffies-dev->jiffies);
dev->jiffies=jiffies;
if (dev->us>=1000000) {
dev->us-=1000000;
dev->s++;
if (dev->s>=60) {
dev->s-=60;
dev->m++;
if (dev->m>60) {
dev->m-=60;
dev->h++;
if (dev->h>24)
dev->h-=24;
}
}
}
sprintf(dev->timestr,"%02d:%02d:%02d:%03d",
dev->h,dev->m,dev->s,(dev->us+500)/1000);
dprintk(2,"vivifill at %s: Buffer 0x%08lx size= %d\n",dev->timestr,
(unsigned long)buf->vb.dma.vmalloc,pos);
/* Advice that buffer was filled */
buf->vb.state = STATE_DONE;
buf->vb.field_count++;
do_gettimeofday(&ts);
buf->vb.ts = ts;
list_del(&buf->vb.queue);
wake_up(&buf->vb.done);
}
static int restart_video_queue(struct vivi_dmaqueue *dma_q);
static void vivi_thread_tick(struct vivi_dmaqueue *dma_q)
{
struct vivi_buffer *buf;
struct vivi_dev *dev= container_of(dma_q,struct vivi_dev,vidq);
int bc;
/* Announces videobuf that all went ok */
for (bc = 0;; bc++) {
if (list_empty(&dma_q->active)) {
dprintk(1,"No active queue to serve\n");
break;
}
buf = list_entry(dma_q->active.next,
struct vivi_buffer, vb.queue);
/* Nobody is waiting something to be done, just return */
if (!waitqueue_active(&buf->vb.done)) {
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
return;
}
do_gettimeofday(&buf->vb.ts);
dprintk(2,"[%p/%d] wakeup\n",buf,buf->vb.i);
/* Fill buffer */
vivi_fillbuff(dev,buf);
}
if (list_empty(&dma_q->active)) {
del_timer(&dma_q->timeout);
} else {
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
}
if (bc != 1)
dprintk(1,"%s: %d buffers handled (should be 1)\n",__FUNCTION__,bc);
}
static void vivi_sleep(struct vivi_dmaqueue *dma_q)
{
int timeout;
DECLARE_WAITQUEUE(wait, current);
dprintk(1,"%s dma_q=0x%08lx\n",__FUNCTION__,(unsigned long)dma_q);
add_wait_queue(&dma_q->wq, &wait);
if (!kthread_should_stop()) {
dma_q->frame++;
/* Calculate time to wake up */
timeout=dma_q->ini_jiffies+msecs_to_jiffies((dma_q->frame*WAKE_NUMERATOR*1000)/WAKE_DENOMINATOR)-jiffies;
if (timeout <= 0) {
int old=dma_q->frame;
dma_q->frame=(jiffies_to_msecs(jiffies-dma_q->ini_jiffies)*WAKE_DENOMINATOR)/(WAKE_NUMERATOR*1000)+1;
timeout=dma_q->ini_jiffies+msecs_to_jiffies((dma_q->frame*WAKE_NUMERATOR*1000)/WAKE_DENOMINATOR)-jiffies;
dprintk(1,"underrun, losed %d frames. "
"Now, frame is %d. Waking on %d jiffies\n",
dma_q->frame-old,dma_q->frame,timeout);
} else
dprintk(1,"will sleep for %i jiffies\n",timeout);
vivi_thread_tick(dma_q);
schedule_timeout_interruptible (timeout);
}
remove_wait_queue(&dma_q->wq, &wait);
try_to_freeze();
}
static int vivi_thread(void *data)
{
struct vivi_dmaqueue *dma_q=data;
dprintk(1,"thread started\n");
for (;;) {
vivi_sleep(dma_q);
if (kthread_should_stop())
break;
}
dprintk(1, "thread: exit\n");
return 0;
}
static int vivi_start_thread(struct vivi_dmaqueue *dma_q)
{
dma_q->frame=0;
dma_q->ini_jiffies=jiffies;
dprintk(1,"%s\n",__FUNCTION__);
init_waitqueue_head(&dma_q->wq);
dma_q->kthread = kthread_run(vivi_thread, dma_q, "vivi");
if (IS_ERR(dma_q->kthread)) {
printk(KERN_ERR "vivi: kernel_thread() failed\n");
return PTR_ERR(dma_q->kthread);
}
dprintk(1,"returning from %s\n",__FUNCTION__);
return 0;
}
static void vivi_stop_thread(struct vivi_dmaqueue *dma_q)
{
dprintk(1,"%s\n",__FUNCTION__);
/* shutdown control thread */
if (dma_q->kthread) {
kthread_stop(dma_q->kthread);
dma_q->kthread=NULL;
}
}
static int restart_video_queue(struct vivi_dmaqueue *dma_q)
{
struct vivi_buffer *buf, *prev;
struct list_head *item;
dprintk(1,"%s dma_q=0x%08lx\n",__FUNCTION__,(unsigned long)dma_q);
if (!list_empty(&dma_q->active)) {
buf = list_entry(dma_q->active.next, struct vivi_buffer, vb.queue);
dprintk(2,"restart_queue [%p/%d]: restart dma\n",
buf, buf->vb.i);
dprintk(1,"Restarting video dma\n");
vivi_stop_thread(dma_q);
// vivi_start_thread(dma_q);
/* cancel all outstanding capture / vbi requests */
list_for_each(item,&dma_q->active) {
buf = list_entry(item, struct vivi_buffer, vb.queue);
list_del(&buf->vb.queue);
buf->vb.state = STATE_ERROR;
wake_up(&buf->vb.done);
}
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
return 0;
}
prev = NULL;
for (;;) {
if (list_empty(&dma_q->queued))
return 0;
buf = list_entry(dma_q->queued.next, struct vivi_buffer, vb.queue);
if (NULL == prev) {
list_del(&buf->vb.queue);
list_add_tail(&buf->vb.queue,&dma_q->active);
dprintk(1,"Restarting video dma\n");
vivi_stop_thread(dma_q);
vivi_start_thread(dma_q);
buf->vb.state = STATE_ACTIVE;
mod_timer(&dma_q->timeout, jiffies+BUFFER_TIMEOUT);
dprintk(2,"[%p/%d] restart_queue - first active\n",
buf,buf->vb.i);
} else if (prev->vb.width == buf->vb.width &&
prev->vb.height == buf->vb.height &&
prev->fmt == buf->fmt) {
list_del(&buf->vb.queue);
list_add_tail(&buf->vb.queue,&dma_q->active);
buf->vb.state = STATE_ACTIVE;
dprintk(2,"[%p/%d] restart_queue - move to active\n",
buf,buf->vb.i);
} else {
return 0;
}
prev = buf;
}
}
static void vivi_vid_timeout(unsigned long data)
{
struct vivi_dev *dev = (struct vivi_dev*)data;
struct vivi_dmaqueue *vidq = &dev->vidq;
struct vivi_buffer *buf;
while (!list_empty(&vidq->active)) {
buf = list_entry(vidq->active.next, struct vivi_buffer, vb.queue);
list_del(&buf->vb.queue);
buf->vb.state = STATE_ERROR;
wake_up(&buf->vb.done);
printk("vivi/0: [%p/%d] timeout\n", buf, buf->vb.i);
}
restart_video_queue(vidq);
}
/* ------------------------------------------------------------------
Videobuf operations
------------------------------------------------------------------*/
static int
buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
{
struct vivi_fh *fh = vq->priv_data;
*size = fh->width*fh->height*2;
if (0 == *count)
*count = 32;
while (*size * *count > vid_limit * 1024 * 1024)
(*count)--;
return 0;
}
static void free_buffer(struct videobuf_queue *vq, struct vivi_buffer *buf)
{
dprintk(1,"%s\n",__FUNCTION__);
if (in_interrupt())
BUG();
/*FIXME: Maybe a spinlock is required here */
kfree(buf->to_addr);
buf->to_addr=NULL;
videobuf_waiton(&buf->vb,0,0);
videobuf_dma_unmap(vq, &buf->vb.dma);
videobuf_dma_free(&buf->vb.dma);
buf->vb.state = STATE_NEEDS_INIT;
}
#define norm_maxw() 1024
#define norm_maxh() 768
static int
buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
enum v4l2_field field)
{
struct vivi_fh *fh = vq->priv_data;
struct vivi_buffer *buf = container_of(vb,struct vivi_buffer,vb);
int rc, init_buffer = 0;
// dprintk(1,"%s, field=%d\n",__FUNCTION__,field);
BUG_ON(NULL == fh->fmt);
if (fh->width < 48 || fh->width > norm_maxw() ||
fh->height < 32 || fh->height > norm_maxh())
return -EINVAL;
buf->vb.size = fh->width*fh->height*2;
if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
return -EINVAL;
if (buf->fmt != fh->fmt ||
buf->vb.width != fh->width ||
buf->vb.height != fh->height ||
buf->vb.field != field) {
buf->fmt = fh->fmt;
buf->vb.width = fh->width;
buf->vb.height = fh->height;
buf->vb.field = field;
init_buffer = 1;
}
if (STATE_NEEDS_INIT == buf->vb.state) {
if (0 != (rc = videobuf_iolock(vq,&buf->vb,NULL)))
goto fail;
}
buf->vb.state = STATE_PREPARED;
if (NULL == (buf->to_addr = kmalloc(sizeof(*buf->to_addr) * vb->dma.nr_pages,GFP_KERNEL))) {
rc=-ENOMEM;
goto fail;
}
return 0;
fail:
free_buffer(vq,buf);
return rc;
}
static void
buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
struct vivi_buffer *buf = container_of(vb,struct vivi_buffer,vb);
struct vivi_fh *fh = vq->priv_data;
struct vivi_dev *dev = fh->dev;
struct vivi_dmaqueue *vidq = &dev->vidq;
struct vivi_buffer *prev;
if (!list_empty(&vidq->queued)) {
dprintk(1,"adding vb queue=0x%08lx\n",(unsigned long)&buf->vb.queue);
list_add_tail(&buf->vb.queue,&vidq->queued);
buf->vb.state = STATE_QUEUED;
dprintk(2,"[%p/%d] buffer_queue - append to queued\n",
buf, buf->vb.i);
} else if (list_empty(&vidq->active)) {
list_add_tail(&buf->vb.queue,&vidq->active);
buf->vb.state = STATE_ACTIVE;
mod_timer(&vidq->timeout, jiffies+BUFFER_TIMEOUT);
dprintk(2,"[%p/%d] buffer_queue - first active\n",
buf, buf->vb.i);
vivi_start_thread(vidq);
} else {
prev = list_entry(vidq->active.prev, struct vivi_buffer, vb.queue);
if (prev->vb.width == buf->vb.width &&
prev->vb.height == buf->vb.height &&
prev->fmt == buf->fmt) {
list_add_tail(&buf->vb.queue,&vidq->active);
buf->vb.state = STATE_ACTIVE;
dprintk(2,"[%p/%d] buffer_queue - append to active\n",
buf, buf->vb.i);
} else {
list_add_tail(&buf->vb.queue,&vidq->queued);
buf->vb.state = STATE_QUEUED;
dprintk(2,"[%p/%d] buffer_queue - first queued\n",
buf, buf->vb.i);
}
}
}
static void buffer_release(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
struct vivi_buffer *buf = container_of(vb,struct vivi_buffer,vb);
struct vivi_fh *fh = vq->priv_data;
struct vivi_dev *dev = (struct vivi_dev*)fh->dev;
struct vivi_dmaqueue *vidq = &dev->vidq;
dprintk(1,"%s\n",__FUNCTION__);
vivi_stop_thread(vidq);
free_buffer(vq,buf);
}
static int vivi_map_sg(void *dev, struct scatterlist *sg, int nents,
int direction)
{
int i;
dprintk(1,"%s, number of pages=%d\n",__FUNCTION__,nents);
BUG_ON(direction == DMA_NONE);
for (i = 0; i < nents; i++ ) {
BUG_ON(!sg[i].page);
sg_dma_address(&sg[i]) = page_to_phys(sg[i].page) + sg[i].offset;
}
return nents;
}
static int vivi_unmap_sg(void *dev,struct scatterlist *sglist,int nr_pages,
int direction)
{
dprintk(1,"%s\n",__FUNCTION__);
return 0;
}
static int vivi_dma_sync_sg(void *dev,struct scatterlist *sglist, int nr_pages,
int direction)
{
// dprintk(1,"%s\n",__FUNCTION__);
// flush_write_buffers();
return 0;
}
static struct videobuf_queue_ops vivi_video_qops = {
.buf_setup = buffer_setup,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.buf_release = buffer_release,
/* Non-pci handling routines */
.vb_map_sg = vivi_map_sg,
.vb_dma_sync_sg = vivi_dma_sync_sg,
.vb_unmap_sg = vivi_unmap_sg,
};
/* ------------------------------------------------------------------
IOCTL handling
------------------------------------------------------------------*/
static int res_get(struct vivi_dev *dev, struct vivi_fh *fh)
{
/* is it free? */
down(&dev->lock);
if (dev->resources) {
/* no, someone else uses it */
up(&dev->lock);
return 0;
}
/* it's free, grab it */
dev->resources =1;
dprintk(1,"res: get\n");
up(&dev->lock);
return 1;
}
static int res_locked(struct vivi_dev *dev)
{
return (dev->resources);
}
static void res_free(struct vivi_dev *dev, struct vivi_fh *fh)
{
down(&dev->lock);
dev->resources = 0;
dprintk(1,"res: put\n");
up(&dev->lock);
}
/* ------------------------------------------------------------------
IOCTL vidioc handling
------------------------------------------------------------------*/
static int vidioc_querycap (struct file *file, void *priv,
struct v4l2_capability *cap)
{
strcpy(cap->driver, "vivi");
strcpy(cap->card, "vivi");
cap->version = VIVI_VERSION;
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING |
V4L2_CAP_READWRITE;
return 0;
}
static int vidioc_enum_fmt_cap (struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
if (f->index > 0)
return -EINVAL;
strlcpy(f->description,format.name,sizeof(f->description));
f->pixelformat = format.fourcc;
return 0;
}
static int vidioc_g_fmt_cap (struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivi_fh *fh=priv;
f->fmt.pix.width = fh->width;
f->fmt.pix.height = fh->height;
f->fmt.pix.field = fh->vb_vidq.field;
f->fmt.pix.pixelformat = fh->fmt->fourcc;
f->fmt.pix.bytesperline =
(f->fmt.pix.width * fh->fmt->depth) >> 3;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
return (0);
}
static int vidioc_try_fmt_cap (struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivi_fmt *fmt;
enum v4l2_field field;
unsigned int maxw, maxh;
if (format.fourcc != f->fmt.pix.pixelformat) {
dprintk(1,"Fourcc format (0x%08x) invalid. Driver accepts "
"only 0x%08x\n",f->fmt.pix.pixelformat,format.fourcc);
return -EINVAL;
}
fmt=&format;
field = f->fmt.pix.field;
if (field == V4L2_FIELD_ANY) {
// field=V4L2_FIELD_INTERLACED;
field=V4L2_FIELD_SEQ_TB;
} else if (V4L2_FIELD_INTERLACED != field) {
dprintk(1,"Field type invalid.\n");
return -EINVAL;
}
maxw = norm_maxw();
maxh = norm_maxh();
f->fmt.pix.field = field;
if (f->fmt.pix.height < 32)
f->fmt.pix.height = 32;
if (f->fmt.pix.height > maxh)
f->fmt.pix.height = maxh;
if (f->fmt.pix.width < 48)
f->fmt.pix.width = 48;
if (f->fmt.pix.width > maxw)
f->fmt.pix.width = maxw;
f->fmt.pix.width &= ~0x03;
f->fmt.pix.bytesperline =
(f->fmt.pix.width * fmt->depth) >> 3;
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
return 0;
}
/*FIXME: This seems to be generic enough to be at videodev2 */
static int vidioc_s_fmt_cap (struct file *file, void *priv,
struct v4l2_format *f)
{
struct vivi_fh *fh=priv;
int ret = vidioc_try_fmt_cap(file,fh,f);
if (ret < 0)
return (ret);
fh->fmt = &format;
fh->width = f->fmt.pix.width;
fh->height = f->fmt.pix.height;
fh->vb_vidq.field = f->fmt.pix.field;
fh->type = f->type;
return (0);
}
static int vidioc_reqbufs (struct file *file, void *priv, struct v4l2_requestbuffers *p)
{
struct vivi_fh *fh=priv;
return (videobuf_reqbufs(&fh->vb_vidq, p));
}
static int vidioc_querybuf (struct file *file, void *priv, struct v4l2_buffer *p)
{
struct vivi_fh *fh=priv;
return (videobuf_querybuf(&fh->vb_vidq, p));
}
static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p)
{
struct vivi_fh *fh=priv;
return (videobuf_qbuf(&fh->vb_vidq, p));
}
static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p)
{
struct vivi_fh *fh=priv;
return (videobuf_dqbuf(&fh->vb_vidq, p,
file->f_flags & O_NONBLOCK));
}
#ifdef CONFIG_VIDEO_V4L1_COMPAT
static int vidiocgmbuf (struct file *file, void *priv, struct video_mbuf *mbuf)
{
struct vivi_fh *fh=priv;
struct videobuf_queue *q=&fh->vb_vidq;
struct v4l2_requestbuffers req;
unsigned int i;
int ret;
req.type = q->type;
req.count = 8;
req.memory = V4L2_MEMORY_MMAP;
ret = videobuf_reqbufs(q,&req);
if (ret < 0)
return (ret);
mbuf->frames = req.count;
mbuf->size = 0;
for (i = 0; i < mbuf->frames; i++) {
mbuf->offsets[i] = q->bufs[i]->boff;
mbuf->size += q->bufs[i]->bsize;
}
return (0);
}
#endif
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct vivi_fh *fh=priv;
struct vivi_dev *dev = fh->dev;
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (i != fh->type)
return -EINVAL;
if (!res_get(dev,fh))
return -EBUSY;
return (videobuf_streamon(&fh->vb_vidq));
}
static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
struct vivi_fh *fh=priv;
struct vivi_dev *dev = fh->dev;
if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
if (i != fh->type)
return -EINVAL;
videobuf_streamoff(&fh->vb_vidq);
res_free(dev,fh);
return (0);
}
static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *i)
{
return 0;
}
/* only one input in this sample driver */
static int vidioc_enum_input (struct file *file, void *priv,
struct v4l2_input *inp)
{
if (inp->index != 0)
return -EINVAL;
inp->type = V4L2_INPUT_TYPE_CAMERA;
inp->std = V4L2_STD_NTSC_M;
strcpy(inp->name,"Camera");
return (0);
}
static int vidioc_g_input (struct file *file, void *priv, unsigned int *i)
{
*i = 0;
return (0);
}
static int vidioc_s_input (struct file *file, void *priv, unsigned int i)
{
if (i > 0)
return -EINVAL;
return (0);
}
/* --- controls ---------------------------------------------- */
static int vidioc_queryctrl (struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
int i;
for (i = 0; i < ARRAY_SIZE(vivi_qctrl); i++)
if (qc->id && qc->id == vivi_qctrl[i].id) {
memcpy(qc, &(vivi_qctrl[i]),
sizeof(*qc));
return (0);
}
return -EINVAL;
}
static int vidioc_g_ctrl (struct file *file, void *priv,
struct v4l2_control *ctrl)
{
int i;
for (i = 0; i < ARRAY_SIZE(vivi_qctrl); i++)
if (ctrl->id == vivi_qctrl[i].id) {
ctrl->value=qctl_regs[i];
return (0);
}
return -EINVAL;
}
static int vidioc_s_ctrl (struct file *file, void *priv,
struct v4l2_control *ctrl)
{
int i;
for (i = 0; i < ARRAY_SIZE(vivi_qctrl); i++)
if (ctrl->id == vivi_qctrl[i].id) {
if (ctrl->value <
vivi_qctrl[i].minimum
|| ctrl->value >
vivi_qctrl[i].maximum) {
return (-ERANGE);
}
qctl_regs[i]=ctrl->value;
return (0);
}
return -EINVAL;
}
/* ------------------------------------------------------------------
File operations for the device
------------------------------------------------------------------*/
#define line_buf_size(norm) (norm_maxw(norm)*(format.depth+7)/8)
static int vivi_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
struct vivi_dev *h,*dev = NULL;
struct vivi_fh *fh;
struct list_head *list;
enum v4l2_buf_type type = 0;
int i;
printk(KERN_DEBUG "vivi: open called (minor=%d)\n",minor);
list_for_each(list,&vivi_devlist) {
h = list_entry(list, struct vivi_dev, vivi_devlist);
if (h->vfd.minor == minor) {
dev = h;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
}
}
if (NULL == dev)
return -ENODEV;
/* If more than one user, mutex should be added */
dev->users++;
dprintk(1,"open minor=%d type=%s users=%d\n",
minor,v4l2_type_names[type],dev->users);
/* allocate + initialize per filehandle data */
fh = kzalloc(sizeof(*fh),GFP_KERNEL);
if (NULL == fh) {
dev->users--;
return -ENOMEM;
}
file->private_data = fh;
fh->dev = dev;
fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fh->fmt = &format;
fh->width = 640;
fh->height = 480;
/* Put all controls at a sane state */
for (i = 0; i < ARRAY_SIZE(vivi_qctrl); i++)
qctl_regs[i] =vivi_qctrl[i].default_value;
dprintk(1,"Open: fh=0x%08lx, dev=0x%08lx, dev->vidq=0x%08lx\n",
(unsigned long)fh,(unsigned long)dev,(unsigned long)&dev->vidq);
dprintk(1,"Open: list_empty queued=%d\n",list_empty(&dev->vidq.queued));
dprintk(1,"Open: list_empty active=%d\n",list_empty(&dev->vidq.active));
/* Resets frame counters */
dev->h=0;
dev->m=0;
dev->s=0;
dev->us=0;
dev->jiffies=jiffies;
sprintf(dev->timestr,"%02d:%02d:%02d:%03d",
dev->h,dev->m,dev->s,(dev->us+500)/1000);
videobuf_queue_init(&fh->vb_vidq, &vivi_video_qops,
NULL, NULL,
fh->type,
V4L2_FIELD_INTERLACED,
sizeof(struct vivi_buffer),fh);
return 0;
}
static ssize_t
vivi_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
{
struct vivi_fh *fh = file->private_data;
if (fh->type==V4L2_BUF_TYPE_VIDEO_CAPTURE) {
if (res_locked(fh->dev))
return -EBUSY;
return videobuf_read_one(&fh->vb_vidq, data, count, ppos,
file->f_flags & O_NONBLOCK);
}
return 0;
}
static unsigned int
vivi_poll(struct file *file, struct poll_table_struct *wait)
{
struct vivi_fh *fh = file->private_data;
struct vivi_buffer *buf;
dprintk(1,"%s\n",__FUNCTION__);
if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type)
return POLLERR;
if (res_get(fh->dev,fh)) {
dprintk(1,"poll: mmap interface\n");
/* streaming capture */
if (list_empty(&fh->vb_vidq.stream))
return POLLERR;
buf = list_entry(fh->vb_vidq.stream.next,struct vivi_buffer,vb.stream);
} else {
dprintk(1,"poll: read() interface\n");
/* read() capture */
buf = (struct vivi_buffer*)fh->vb_vidq.read_buf;
if (NULL == buf)
return POLLERR;
}
poll_wait(file, &buf->vb.done, wait);
if (buf->vb.state == STATE_DONE ||
buf->vb.state == STATE_ERROR)
return POLLIN|POLLRDNORM;
return 0;
}
static int vivi_release(struct inode *inode, struct file *file)
{
struct vivi_fh *fh = file->private_data;
struct vivi_dev *dev = fh->dev;
struct vivi_dmaqueue *vidq = &dev->vidq;
int minor = iminor(inode);
vivi_stop_thread(vidq);
videobuf_mmap_free(&fh->vb_vidq);
kfree (fh);
dev->users--;
printk(KERN_DEBUG "vivi: close called (minor=%d, users=%d)\n",minor,dev->users);
return 0;
}
static int
vivi_mmap(struct file *file, struct vm_area_struct * vma)
{
struct vivi_fh *fh = file->private_data;
int ret;
dprintk (1,"mmap called, vma=0x%08lx\n",(unsigned long)vma);
ret=videobuf_mmap_mapper(&fh->vb_vidq, vma);
dprintk (1,"vma start=0x%08lx, size=%ld, ret=%d\n",
(unsigned long)vma->vm_start,
(unsigned long)vma->vm_end-(unsigned long)vma->vm_start,
ret);
return ret;
}
static struct file_operations vivi_fops = {
.owner = THIS_MODULE,
.open = vivi_open,
.release = vivi_release,
.read = vivi_read,
.poll = vivi_poll,
.ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
.llseek = no_llseek,
};
static struct video_device vivi = {
.name = "vivi",
.type = VID_TYPE_CAPTURE,
.hardware = 0,
.fops = &vivi_fops,
.minor = -1,
// .release = video_device_release,
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_cap = vidioc_enum_fmt_cap,
.vidioc_g_fmt_cap = vidioc_g_fmt_cap,
.vidioc_try_fmt_cap = vidioc_try_fmt_cap,
.vidioc_s_fmt_cap = vidioc_s_fmt_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_s_std = vidioc_s_std,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
#ifdef CONFIG_VIDEO_V4L1_COMPAT
.vidiocgmbuf = vidiocgmbuf,
#endif
.tvnorms = V4L2_STD_NTSC_M,
.current_norm = V4L2_STD_NTSC_M,
};
/* -----------------------------------------------------------------
Initialization and module stuff
------------------------------------------------------------------*/
static int __init vivi_init(void)
{
int ret;
struct vivi_dev *dev;
dev = kzalloc(sizeof(*dev),GFP_KERNEL);
if (NULL == dev)
return -ENOMEM;
list_add_tail(&dev->vivi_devlist,&vivi_devlist);
/* init video dma queues */
INIT_LIST_HEAD(&dev->vidq.active);
INIT_LIST_HEAD(&dev->vidq.queued);
/* initialize locks */
init_MUTEX(&dev->lock);
dev->vidq.timeout.function = vivi_vid_timeout;
dev->vidq.timeout.data = (unsigned long)dev;
init_timer(&dev->vidq.timeout);
ret = video_register_device(&vivi, VFL_TYPE_GRABBER, video_nr);
printk(KERN_INFO "Video Technology Magazine Virtual Video Capture Board (Load status: %d)\n", ret);
return ret;
}
static void __exit vivi_exit(void)
{
struct vivi_dev *h;
struct list_head *list;
while (!list_empty(&vivi_devlist)) {
list = vivi_devlist.next;
list_del(list);
h = list_entry(list, struct vivi_dev, vivi_devlist);
kfree (h);
}
video_unregister_device(&vivi);
}
module_init(vivi_init);
module_exit(vivi_exit);
MODULE_DESCRIPTION("Video Technology Magazine Virtual Video Capture Board");
MODULE_AUTHOR("Mauro Carvalho Chehab, Ted Walther and John Sokol");
MODULE_LICENSE("Dual BSD/GPL");
module_param(video_nr, int, 0);
module_param_named(debug,vivi.debug, int, 0644);
MODULE_PARM_DESC(debug,"activates debug info");
module_param(vid_limit,int,0644);
MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes");