Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad0ebb96 authored by Mauro Carvalho Chehab's avatar Mauro Carvalho Chehab
Browse files

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: default avatarMauro Carvalho Chehab <mchehab@infradead.org>
parent 78e92006
Loading
Loading
Loading
Loading
+0 −417
Original line number Diff line number Diff line
@@ -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;
+743 −455

File changed.

Preview size limit exceeded, changes collapsed.

+68 −26
Original line number Diff line number Diff line
@@ -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 */