V4L/DVB (7540): em28xx: convert to use videobuf-vmalloc

The usage of videobuf-vmalloc allows to cleanup em28xx logic.

Also, it reduced its size by about 5.42% on i386 arch (and about 7.5% on x86_64):

  39113    4876      40   44029    abfd old/em28xx.ko
  36731    4868      40   41639    a2a7 /home/v4l/master/v4l/em28xx.ko

Also, the preliminary tests, made on a single core 1.5 MHz Centrino showed
that CPU usage reduced from 42%-75% to 28%-33% (reports from "top") command.

A test with time command presented an even better result:

This is the performance tests I did, running code_example to get 1,000 frames
@29.995 Hz (about 35 seconds of stream), tested on a i386 machine, running at
1,5GHz:

	The old driver:

$ time -f "%E: %Us User time, %Ss Kernel time, %P CPU used" ./capture_example
0:34.21: 8.22s User time, 25.16s Kernel time, 97% CPU used

	The videobuf-based driver:

$ time -f "%E: %Us User time, %Ss Kernel time, %P CPU used" ./capture_example
0:35.36: 0.01s User time, 0.05s Kernel time, 0% CPU used

	Conclusion:

The time consumption to receive the stream where reduced from about 33.38
seconds to 0.05 seconds.

Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
This commit is contained in:
Mauro Carvalho Chehab 2008-04-13 14:37:52 -03:00
parent 78e92006f4
commit ad0ebb96c2
3 changed files with 813 additions and 900 deletions

View file

@ -49,87 +49,10 @@ MODULE_PARM_DESC(reg_debug,"enable debug messages [URB reg]");
printk(KERN_INFO "%s %s :"fmt, \
dev->name, __func__ , ##arg); } while (0)
static unsigned int isoc_debug;
module_param(isoc_debug,int,0644);
MODULE_PARM_DESC(isoc_debug,"enable debug messages [isoc transfers]");
#define em28xx_isocdbg(fmt, arg...) do {\
if (isoc_debug) \
printk(KERN_INFO "%s %s :"fmt, \
dev->name, __func__ , ##arg); } while (0)
static int alt = EM28XX_PINOUT;
module_param(alt, int, 0644);
MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint");
/*
* em28xx_request_buffers()
* allocate a number of buffers
*/
u32 em28xx_request_buffers(struct em28xx *dev, u32 count)
{
const size_t imagesize = PAGE_ALIGN(dev->frame_size); /*needs to be page aligned cause the buffers can be mapped individually! */
void *buff = NULL;
u32 i;
em28xx_coredbg("requested %i buffers with size %zi\n",
count, imagesize);
if (count > EM28XX_NUM_FRAMES)
count = EM28XX_NUM_FRAMES;
dev->num_frames = count;
while (dev->num_frames > 0) {
if ((buff = vmalloc_32(dev->num_frames * imagesize))) {
memset(buff, 0, dev->num_frames * imagesize);
break;
}
dev->num_frames--;
}
for (i = 0; i < dev->num_frames; i++) {
dev->frame[i].bufmem = buff + i * imagesize;
dev->frame[i].buf.index = i;
dev->frame[i].buf.m.offset = i * imagesize;
dev->frame[i].buf.length = dev->frame_size;
dev->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
dev->frame[i].buf.sequence = 0;
dev->frame[i].buf.field = V4L2_FIELD_NONE;
dev->frame[i].buf.memory = V4L2_MEMORY_MMAP;
dev->frame[i].buf.flags = 0;
}
return dev->num_frames;
}
/*
* em28xx_queue_unusedframes()
* add all frames that are not currently in use to the inbuffer queue
*/
void em28xx_queue_unusedframes(struct em28xx *dev)
{
unsigned long lock_flags;
u32 i;
for (i = 0; i < dev->num_frames; i++)
if (dev->frame[i].state == F_UNUSED) {
dev->frame[i].state = F_QUEUED;
spin_lock_irqsave(&dev->queue_lock, lock_flags);
list_add_tail(&dev->frame[i].frame, &dev->inqueue);
spin_unlock_irqrestore(&dev->queue_lock, lock_flags);
}
}
/*
* em28xx_release_buffers()
* free frame buffers
*/
void em28xx_release_buffers(struct em28xx *dev)
{
if (dev->num_frames) {
vfree(dev->frame[0].bufmem);
dev->num_frames = 0;
}
}
/*
* em28xx_read_reg_req()
* reads data from the usb device specifying bRequest
@ -469,346 +392,6 @@ int em28xx_resolution_set(struct em28xx *dev)
return em28xx_scaler_set(dev, dev->hscale, dev->vscale);
}
/******************* isoc transfer handling ****************************/
#ifdef ENABLE_DEBUG_ISOC_FRAMES
static void em28xx_isoc_dump(struct urb *urb)
{
int len = 0;
int ntrans = 0;
int i;
printk(KERN_DEBUG "isocIrq: sf=%d np=%d ec=%x\n",
urb->start_frame, urb->number_of_packets,
urb->error_count);
for (i = 0; i < urb->number_of_packets; i++) {
unsigned char *buf =
urb->transfer_buffer +
urb->iso_frame_desc[i].offset;
int alen = urb->iso_frame_desc[i].actual_length;
if (alen > 0) {
if (buf[0] == 0x88) {
ntrans++;
len += alen;
} else if (buf[0] == 0x22) {
printk(KERN_DEBUG
"= l=%d nt=%d bpp=%d\n",
len - 4 * ntrans, ntrans,
ntrans == 0 ? 0 : len / ntrans);
ntrans = 1;
len = alen;
} else
printk(KERN_DEBUG "!\n");
}
printk(KERN_DEBUG " n=%d s=%d al=%d %x\n", i,
urb->iso_frame_desc[i].status,
urb->iso_frame_desc[i].actual_length,
(unsigned int)
*((unsigned char *)(urb->transfer_buffer +
urb->iso_frame_desc[i].
offset)));
}
}
#endif
static inline int em28xx_isoc_video(struct em28xx *dev,struct em28xx_frame_t **f,
unsigned long *lock_flags, unsigned char buf)
{
if (!(buf & 0x01)) {
if ((*f)->state == F_GRABBING) {
/*previous frame is incomplete */
if ((*f)->fieldbytesused < dev->field_size) {
(*f)->state = F_ERROR;
em28xx_isocdbg ("dropping incomplete bottom field (%i missing bytes)",
dev->field_size-(*f)->fieldbytesused);
} else {
(*f)->state = F_DONE;
(*f)->buf.bytesused = dev->frame_size;
}
}
if ((*f)->state == F_DONE || (*f)->state == F_ERROR) {
/* move current frame to outqueue and get next free buffer from inqueue */
spin_lock_irqsave(&dev-> queue_lock, *lock_flags);
list_move_tail(&(*f)->frame, &dev->outqueue);
if (!list_empty(&dev->inqueue))
(*f) = list_entry(dev-> inqueue.next,
struct em28xx_frame_t,frame);
else
(*f) = NULL;
spin_unlock_irqrestore(&dev->queue_lock,*lock_flags);
}
if (!(*f)) {
em28xx_isocdbg ("new frame but no buffer is free");
return -1;
}
do_gettimeofday(&(*f)->buf.timestamp);
(*f)->buf.sequence = ++dev->frame_count;
(*f)->buf.field = V4L2_FIELD_INTERLACED;
(*f)->state = F_GRABBING;
(*f)->buf.bytesused = 0;
(*f)->top_field = 1;
(*f)->fieldbytesused = 0;
} else {
/* acquiring bottom field */
if ((*f)->state == F_GRABBING) {
if (!(*f)->top_field) {
(*f)->state = F_ERROR;
em28xx_isocdbg ("unexpected begin of bottom field; discarding it");
} else if ((*f)-> fieldbytesused < dev->field_size - 172) {
(*f)->state = F_ERROR;
em28xx_isocdbg ("dropping incomplete top field (%i missing bytes)",
dev->field_size-(*f)->fieldbytesused);
} else {
(*f)->top_field = 0;
(*f)->fieldbytesused = 0;
}
}
}
return (0);
}
static inline void em28xx_isoc_video_copy(struct em28xx *dev,
struct em28xx_frame_t **f, unsigned char *buf, int len)
{
void *fieldstart, *startwrite, *startread;
int linesdone, currlinedone, offset, lencopy,remain;
if(dev->frame_size != (*f)->buf.length){
em28xx_err("frame_size %i and buf.length %i are different!!!\n",dev->frame_size,(*f)->buf.length);
return;
}
if ((*f)->fieldbytesused + len > dev->field_size)
len =dev->field_size - (*f)->fieldbytesused;
if (buf[0] != 0x88 && buf[0] != 0x22) {
em28xx_isocdbg("frame is not complete\n");
startread = buf;
len+=4;
} else
startread = buf + 4;
remain = len;
if ((*f)->top_field)
fieldstart = (*f)->bufmem;
else
fieldstart = (*f)->bufmem + dev->bytesperline;
linesdone = (*f)->fieldbytesused / dev->bytesperline;
currlinedone = (*f)->fieldbytesused % dev->bytesperline;
offset = linesdone * dev->bytesperline * 2 + currlinedone;
startwrite = fieldstart + offset;
lencopy = dev->bytesperline - currlinedone;
lencopy = lencopy > remain ? remain : lencopy;
memcpy(startwrite, startread, lencopy);
remain -= lencopy;
while (remain > 0) {
startwrite += lencopy + dev->bytesperline;
startread += lencopy;
if (dev->bytesperline > remain)
lencopy = remain;
else
lencopy = dev->bytesperline;
memcpy(startwrite, startread, lencopy);
remain -= lencopy;
}
(*f)->fieldbytesused += len;
}
/*
* em28xx_isoIrq()
* handles the incoming isoc urbs and fills the frames from our inqueue
*/
static void em28xx_isocIrq(struct urb *urb)
{
struct em28xx *dev = urb->context;
int i, status;
struct em28xx_frame_t **f;
unsigned long lock_flags;
if (!dev)
return;
#ifdef ENABLE_DEBUG_ISOC_FRAMES
if (isoc_debug>1)
em28xx_isoc_dump(urb);
#endif
if (urb->status == -ENOENT)
return;
f = &dev->frame_current;
if (dev->stream == STREAM_INTERRUPT) {
dev->stream = STREAM_OFF;
if ((*f))
(*f)->state = F_QUEUED;
em28xx_isocdbg("stream interrupted");
wake_up_interruptible(&dev->wait_stream);
}
if ((dev->state & DEV_DISCONNECTED) || (dev->state & DEV_MISCONFIGURED))
return;
if (dev->stream == STREAM_ON && !list_empty(&dev->inqueue)) {
if (!(*f))
(*f) = list_entry(dev->inqueue.next,
struct em28xx_frame_t, frame);
for (i = 0; i < urb->number_of_packets; i++) {
unsigned char *buf = urb->transfer_buffer +
urb->iso_frame_desc[i].offset;
int len = urb->iso_frame_desc[i].actual_length - 4;
if (urb->iso_frame_desc[i].status) {
em28xx_isocdbg("data error: [%d] len=%d, status=%d", i,
urb->iso_frame_desc[i].actual_length,
urb->iso_frame_desc[i].status);
if (urb->iso_frame_desc[i].status != -EPROTO)
continue;
}
if (urb->iso_frame_desc[i].actual_length <= 0) {
em28xx_isocdbg("packet %d is empty",i);
continue;
}
if (urb->iso_frame_desc[i].actual_length >
urb->iso_frame_desc[i].length) {
em28xx_isocdbg("packet bigger than packet size");
continue;
}
/*new frame */
if (buf[0] == 0x22 && buf[1] == 0x5a) {
em28xx_isocdbg("Video frame, length=%i!",len);
if (em28xx_isoc_video(dev,f,&lock_flags,buf[2]))
break;
} else if (buf[0]==0x33 && buf[1]==0x95 && buf[2]==0x00) {
em28xx_isocdbg("VBI HEADER!!!");
}
/* actual copying */
if ((*f)->state == F_GRABBING) {
em28xx_isoc_video_copy(dev,f,buf, len);
}
}
}
for (i = 0; i < urb->number_of_packets; i++) {
urb->iso_frame_desc[i].status = 0;
urb->iso_frame_desc[i].actual_length = 0;
}
urb->status = 0;
if ((status = usb_submit_urb(urb, GFP_ATOMIC))) {
em28xx_errdev("resubmit of urb failed (error=%i)\n", status);
dev->state |= DEV_MISCONFIGURED;
}
wake_up_interruptible(&dev->wait_frame);
return;
}
/*
* em28xx_uninit_isoc()
* deallocates the buffers and urbs allocated during em28xx_init_iosc()
*/
void em28xx_uninit_isoc(struct em28xx *dev)
{
int i;
for (i = 0; i < EM28XX_NUM_BUFS; i++) {
if (dev->urb[i]) {
usb_kill_urb(dev->urb[i]);
if (dev->transfer_buffer[i]) {
usb_buffer_free(dev->udev,
dev->urb[i]->transfer_buffer_length,
dev->transfer_buffer[i],
dev->urb[i]->transfer_dma);
}
usb_free_urb(dev->urb[i]);
}
dev->urb[i] = NULL;
dev->transfer_buffer[i] = NULL;
}
em28xx_capture_start(dev, 0);
}
/*
* em28xx_init_isoc()
* allocates transfer buffers and submits the urbs for isoc transfer
*/
int em28xx_init_isoc(struct em28xx *dev)
{
/* change interface to 3 which allows the biggest packet sizes */
int i, errCode;
int sb_size;
em28xx_set_alternate(dev);
sb_size = EM28XX_NUM_PACKETS * dev->max_pkt_size;
/* reset streaming vars */
dev->frame_current = NULL;
dev->frame_count = 0;
/* allocate urbs */
for (i = 0; i < EM28XX_NUM_BUFS; i++) {
struct urb *urb;
int j;
/* allocate transfer buffer */
urb = usb_alloc_urb(EM28XX_NUM_PACKETS, GFP_KERNEL);
if (!urb){
em28xx_errdev("cannot alloc urb %i\n", i);
em28xx_uninit_isoc(dev);
return -ENOMEM;
}
dev->transfer_buffer[i] = usb_buffer_alloc(dev->udev, sb_size,
GFP_KERNEL,
&urb->transfer_dma);
if (!dev->transfer_buffer[i]) {
em28xx_errdev
("unable to allocate %i bytes for transfer buffer %i\n",
sb_size, i);
em28xx_uninit_isoc(dev);
usb_free_urb(urb);
return -ENOMEM;
}
memset(dev->transfer_buffer[i], 0, sb_size);
urb->dev = dev->udev;
urb->context = dev;
urb->pipe = usb_rcvisocpipe(dev->udev, 0x82);
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->interval = 1;
urb->transfer_buffer = dev->transfer_buffer[i];
urb->complete = em28xx_isocIrq;
urb->number_of_packets = EM28XX_NUM_PACKETS;
urb->transfer_buffer_length = sb_size;
for (j = 0; j < EM28XX_NUM_PACKETS; j++) {
urb->iso_frame_desc[j].offset = j * dev->max_pkt_size;
urb->iso_frame_desc[j].length = dev->max_pkt_size;
}
dev->urb[i] = urb;
}
/* submit urbs */
em28xx_coredbg("Submitting %d urbs of %d packets (%d each)\n",
EM28XX_NUM_BUFS, EM28XX_NUM_PACKETS, dev->max_pkt_size);
for (i = 0; i < EM28XX_NUM_BUFS; i++) {
errCode = usb_submit_urb(dev->urb[i], GFP_KERNEL);
if (errCode) {
em28xx_errdev("submit of urb %i failed (error=%i)\n", i,
errCode);
em28xx_uninit_isoc(dev);
return errCode;
}
}
return 0;
}
int em28xx_set_alternate(struct em28xx *dev)
{
int errCode, prev_alt = dev->alt;

File diff suppressed because it is too large Load diff

View file

@ -26,12 +26,12 @@
#define _EM28XX_H
#include <linux/videodev2.h>
#include <media/videobuf-vmalloc.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <media/ir-kbd-i2c.h>
#define UNSET -1
/* maximum number of em28xx boards */
#define EM28XX_MAXBOARDS 4 /*FIXME: should be bigger */
@ -81,31 +81,69 @@
/* time in msecs to wait for i2c writes to finish */
#define EM2800_I2C_WRITE_TIMEOUT 20
/* the various frame states */
enum em28xx_frame_state {
F_UNUSED = 0,
F_QUEUED,
F_GRABBING,
F_DONE,
F_ERROR,
};
/* stream states */
enum em28xx_stream_state {
STREAM_OFF,
STREAM_INTERRUPT,
STREAM_ON,
};
/* frames */
struct em28xx_frame_t {
void *bufmem;
struct v4l2_buffer buf;
enum em28xx_frame_state state;
struct em28xx_usb_isoc_ctl {
/* max packet size of isoc transaction */
int max_pkt_size;
/* number of allocated urbs */
int num_bufs;
/* urb for isoc transfers */
struct urb **urb;
/* transfer buffers for isoc transfer */
char **transfer_buffer;
/* Last buffer command and region */
u8 cmd;
int pos, size, pktsize;
/* Last field: ODD or EVEN? */
int field;
/* Stores incomplete commands */
u32 tmp_buf;
int tmp_buf_len;
/* Stores already requested buffers */
struct em28xx_buffer *buf;
/* Stores the number of received fields */
int nfields;
};
struct em28xx_fmt {
char *name;
u32 fourcc; /* v4l2 format id */
};
/* buffer for one video frame */
struct em28xx_buffer {
/* common v4l buffer stuff -- must be first */
struct videobuf_buffer vb;
struct em28xx_fmt *fmt;
struct list_head frame;
unsigned long vma_use_count;
int top_field;
int fieldbytesused;
int receiving;
};
struct em28xx_dmaqueue {
struct list_head active;
struct list_head queued;
struct timer_list timeout;
wait_queue_head_t wq;
/* Counters to control buffer fill */
int pos;
};
/* io methods */
@ -255,10 +293,6 @@ struct em28xx {
int mute;
int volume;
/* frame properties */
struct em28xx_frame_t frame[EM28XX_NUM_FRAMES]; /* list of frames */
int num_frames; /* number of frames currently in use */
unsigned int frame_count; /* total number of transfered frames */
struct em28xx_frame_t *frame_current; /* the frame that is being filled */
int width; /* current frame width */
int height; /* current frame height */
int frame_size; /* current frame size */
@ -277,7 +311,6 @@ struct em28xx {
/* states */
enum em28xx_dev_state state;
enum em28xx_stream_state stream;
enum em28xx_io_method io;
struct work_struct request_module_wk;
@ -292,6 +325,11 @@ struct em28xx {
unsigned char eedata[256];
/* Isoc control struct */
struct em28xx_dmaqueue vidq;
struct em28xx_usb_isoc_ctl isoc_ctl;
spinlock_t slock;
/* usb transfer */
struct usb_device *udev; /* the usb device */
int alt; /* alternate */
@ -315,6 +353,12 @@ struct em28xx_fh {
struct em28xx *dev;
unsigned int stream_on:1; /* Locks streams */
int radio;
unsigned int width, height;
struct videobuf_queue vb_vidq;
struct em28xx_fmt *fmt;
enum v4l2_buf_type type;
};
struct em28xx_ops {
@ -351,8 +395,6 @@ int em28xx_colorlevels_set_default(struct em28xx *dev);
int em28xx_capture_start(struct em28xx *dev, int start);
int em28xx_outfmt_set_yuv422(struct em28xx *dev);
int em28xx_resolution_set(struct em28xx *dev);
int em28xx_init_isoc(struct em28xx *dev);
void em28xx_uninit_isoc(struct em28xx *dev);
int em28xx_set_alternate(struct em28xx *dev);
/* Provided by em28xx-video.c */