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

Commit 52fcb3ec authored by Andy Walls's avatar Andy Walls Committed by Mauro Carvalho Chehab
Browse files

V4L/DVB (13429): cx18: Add Memory Descriptor List (MDL) layer to buffer handling



Add a Memory Descriptor List (MDL) layer to buffer handling to implement
scatter-gather I/O.  Currently there is still only 1 buffer per MDL.

Signed-off-by: default avatarAndy Walls <awalls@radix.net>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent fa655dda
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -669,6 +669,12 @@ static int __devinit cx18_init_struct1(struct cx18 *cx)
	cx->vbi.in.type = V4L2_BUF_TYPE_VBI_CAPTURE;
	cx->vbi.sliced_in = &cx->vbi.in.fmt.sliced;

	/* IVTV style VBI insertion into MPEG streams */
	INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_buf.list);
	INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.list);
	INIT_LIST_HEAD(&cx->vbi.sliced_mpeg_mdl.buf_list);
	list_add(&cx->vbi.sliced_mpeg_buf.list,
		 &cx->vbi.sliced_mpeg_mdl.buf_list);
	return 0;
}

+31 −14
Original line number Diff line number Diff line
@@ -246,8 +246,8 @@ struct cx18_options {
	int radio;		/* enable/disable radio */
};

/* per-buffer bit flags */
#define CX18_F_B_NEED_BUF_SWAP  0	/* this buffer should be byte swapped */
/* per-mdl bit flags */
#define CX18_F_M_NEED_SWAP  0	/* mdl buffer data must be endianess swapped */

/* per-stream, s_flags */
#define CX18_F_S_CLAIMED 	3	/* this stream is claimed */
@@ -274,15 +274,26 @@ struct cx18_options {
struct cx18_buffer {
	struct list_head list;
	dma_addr_t dma_handle;
	u32 id;
	unsigned long b_flags;
	unsigned skipped;
	char *buf;

	u32 bytesused;
	u32 readpos;
};

struct cx18_mdl {
	struct list_head list;
	u32 id;		/* index into cx->scb->cpu_mdl[] of 1st cx18_mdl_ent */

	unsigned int skipped;
	unsigned long m_flags;

	struct list_head buf_list;
	struct cx18_buffer *curr_buf; /* current buffer in list for reading */

	u32 bytesused;
	u32 readpos;
};

struct cx18_queue {
	struct list_head list;
	atomic_t depth;
@@ -346,14 +357,20 @@ struct cx18_stream {
				   PCI_DMA_NONE */
	wait_queue_head_t waitq;

	/* Buffer Stats */
	u32 buffers;
	u32 buf_size;
	/* Buffers */
	struct list_head buf_pool;	/* buffers not attached to an MDL */
	u32 buffers;			/* total buffers owned by this stream */
	u32 buf_size;			/* size in bytes of a single buffer */

	/* MDL sizes - all stream MDLs are the same size */
	u32 bufs_per_mdl;
	u32 mdl_size;		/* total bytes in all buffers in a mdl */

	/* Buffer Queues */
	struct cx18_queue q_free;	/* free buffers */
	struct cx18_queue q_busy;	/* busy buffers - in use by firmware */
	struct cx18_queue q_full;	/* full buffers - data for user apps */
	/* MDL Queues */
	struct cx18_queue q_free;	/* free - in rotation, not committed */
	struct cx18_queue q_busy;	/* busy - in use by firmware */
	struct cx18_queue q_full;	/* full - data for user apps */
	struct cx18_queue q_idle;	/* idle - not in rotation */

	struct work_struct out_work_order;

@@ -481,10 +498,11 @@ struct vbi_info {
	u32 inserted_frame;

	/*
	 * A dummy driver stream transfer buffer with a copy of the next
	 * A dummy driver stream transfer mdl & buffer with a copy of the next
	 * sliced_mpeg_data[] buffer for output to userland apps.
	 * Only used in cx18-fileops.c, but its state needs to persist at times.
	 */
	struct cx18_mdl sliced_mpeg_mdl;
	struct cx18_buffer sliced_mpeg_buf;
};

@@ -511,7 +529,6 @@ struct cx18 {
	u8 is_60hz;
	u8 nof_inputs;		/* number of video inputs */
	u8 nof_audio_inputs;	/* number of audio inputs */
	u16 buffer_id;		/* buffer ID counter */
	u32 v4l2_cap;		/* V4L2 capabilities of card */
	u32 hw_flags; 		/* Hardware description of the board */
	unsigned int free_mdl_idx;
+96 −32
Original line number Diff line number Diff line
@@ -166,11 +166,12 @@ static void cx18_dualwatch(struct cx18 *cx)
}


static struct cx18_buffer *cx18_get_buffer(struct cx18_stream *s, int non_block, int *err)
static struct cx18_mdl *cx18_get_mdl(struct cx18_stream *s, int non_block,
				     int *err)
{
	struct cx18 *cx = s->cx;
	struct cx18_stream *s_vbi = &cx->streams[CX18_ENC_STREAM_TYPE_VBI];
	struct cx18_buffer *buf;
	struct cx18_mdl *mdl;
	DEFINE_WAIT(wait);

	*err = 0;
@@ -185,32 +186,33 @@ static struct cx18_buffer *cx18_get_buffer(struct cx18_stream *s, int non_block,
			}
			if (test_bit(CX18_F_S_INTERNAL_USE, &s_vbi->s_flags) &&
			    !test_bit(CX18_F_S_APPL_IO, &s_vbi->s_flags)) {
				while ((buf = cx18_dequeue(s_vbi, &s_vbi->q_full))) {
				while ((mdl = cx18_dequeue(s_vbi,
							   &s_vbi->q_full))) {
					/* byteswap and process VBI data */
					cx18_process_vbi_data(cx, buf,
					cx18_process_vbi_data(cx, mdl,
							      s_vbi->type);
					cx18_stream_put_buf_fw(s_vbi, buf);
					cx18_stream_put_mdl_fw(s_vbi, mdl);
				}
			}
			buf = &cx->vbi.sliced_mpeg_buf;
			if (buf->readpos != buf->bytesused)
				return buf;
			mdl = &cx->vbi.sliced_mpeg_mdl;
			if (mdl->readpos != mdl->bytesused)
				return mdl;
		}

		/* do we have new data? */
		buf = cx18_dequeue(s, &s->q_full);
		if (buf) {
			if (!test_and_clear_bit(CX18_F_B_NEED_BUF_SWAP,
						&buf->b_flags))
				return buf;
		mdl = cx18_dequeue(s, &s->q_full);
		if (mdl) {
			if (!test_and_clear_bit(CX18_F_M_NEED_SWAP,
						&mdl->m_flags))
				return mdl;
			if (s->type == CX18_ENC_STREAM_TYPE_MPG)
				/* byteswap MPG data */
				cx18_buf_swap(buf);
				cx18_mdl_swap(mdl);
			else {
				/* byteswap and process VBI data */
				cx18_process_vbi_data(cx, buf, s->type);
				cx18_process_vbi_data(cx, mdl, s->type);
			}
			return buf;
			return mdl;
		}

		/* return if end of stream */
@@ -241,21 +243,28 @@ static struct cx18_buffer *cx18_get_buffer(struct cx18_stream *s, int non_block,
	}
}

static void cx18_setup_sliced_vbi_buf(struct cx18 *cx)
static void cx18_setup_sliced_vbi_mdl(struct cx18 *cx)
{
	struct cx18_mdl *mdl = &cx->vbi.sliced_mpeg_mdl;
	struct cx18_buffer *buf = &cx->vbi.sliced_mpeg_buf;
	int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;

	cx->vbi.sliced_mpeg_buf.buf = cx->vbi.sliced_mpeg_data[idx];
	cx->vbi.sliced_mpeg_buf.bytesused = cx->vbi.sliced_mpeg_size[idx];
	cx->vbi.sliced_mpeg_buf.readpos = 0;
	buf->buf = cx->vbi.sliced_mpeg_data[idx];
	buf->bytesused = cx->vbi.sliced_mpeg_size[idx];
	buf->readpos = 0;

	mdl->curr_buf = NULL;
	mdl->bytesused = cx->vbi.sliced_mpeg_size[idx];
	mdl->readpos = 0;
}

static size_t cx18_copy_buf_to_user(struct cx18_stream *s,
		struct cx18_buffer *buf, char __user *ubuf, size_t ucount)
	struct cx18_buffer *buf, char __user *ubuf, size_t ucount, bool *stop)
{
	struct cx18 *cx = s->cx;
	size_t len = buf->bytesused - buf->readpos;

	*stop = false;
	if (len > ucount)
		len = ucount;
	if (cx->vbi.insert_mpeg && s->type == CX18_ENC_STREAM_TYPE_MPG &&
@@ -335,7 +344,8 @@ static size_t cx18_copy_buf_to_user(struct cx18_stream *s,
				/* We declare we actually found a Program Pack*/
				cx->search_pack_header = 0; /* expect vid PES */
				len = (char *)q - start;
				cx18_setup_sliced_vbi_buf(cx);
				cx18_setup_sliced_vbi_mdl(cx);
				*stop = true;
				break;
			}
		}
@@ -352,6 +362,60 @@ static size_t cx18_copy_buf_to_user(struct cx18_stream *s,
	return len;
}

/**
 * list_entry_is_past_end - check if a previous loop cursor is off list end
 * @pos:	the type * previously used as a loop cursor.
 * @head:	the head for your list.
 * @member:	the name of the list_struct within the struct.
 *
 * Check if the entry's list_head is the head of the list, thus it's not a
 * real entry but was the loop cursor that walked past the end
 */
#define list_entry_is_past_end(pos, head, member) \
	(&pos->member == (head))

static size_t cx18_copy_mdl_to_user(struct cx18_stream *s,
		struct cx18_mdl *mdl, char __user *ubuf, size_t ucount)
{
	size_t tot_written = 0;
	int rc;
	bool stop = false;

	if (mdl->curr_buf == NULL)
		mdl->curr_buf = list_first_entry(&mdl->buf_list,
						 struct cx18_buffer, list);

	if (list_entry_is_past_end(mdl->curr_buf, &mdl->buf_list, list)) {
		/*
		 * For some reason we've exhausted the buffers, but the MDL
		 * object still said some data was unread.
		 * Fix that and bail out.
		 */
		mdl->readpos = mdl->bytesused;
		return 0;
	}

	list_for_each_entry_from(mdl->curr_buf, &mdl->buf_list, list) {

		if (mdl->curr_buf->readpos >= mdl->curr_buf->bytesused)
			continue;

		rc = cx18_copy_buf_to_user(s, mdl->curr_buf, ubuf + tot_written,
					   ucount - tot_written, &stop);
		if (rc < 0)
			return rc;
		mdl->readpos += rc;
		tot_written += rc;

		if (stop ||	/* Forced stopping point for VBI insertion */
		    tot_written >= ucount ||	/* Reader request statisfied */
		    mdl->curr_buf->readpos < mdl->curr_buf->bytesused ||
		    mdl->readpos >= mdl->bytesused) /* MDL buffers drained */
			break;
	}
	return tot_written;
}

static ssize_t cx18_read(struct cx18_stream *s, char __user *ubuf,
		size_t tot_count, int non_block)
{
@@ -373,12 +437,12 @@ static ssize_t cx18_read(struct cx18_stream *s, char __user *ubuf,
		single_frame = 1;

	for (;;) {
		struct cx18_buffer *buf;
		struct cx18_mdl *mdl;
		int rc;

		buf = cx18_get_buffer(s, non_block, &rc);
		mdl = cx18_get_mdl(s, non_block, &rc);
		/* if there is no data available... */
		if (buf == NULL) {
		if (mdl == NULL) {
			/* if we got data, then return that regardless */
			if (tot_written)
				break;
@@ -392,20 +456,20 @@ static ssize_t cx18_read(struct cx18_stream *s, char __user *ubuf,
			return rc;
		}

		rc = cx18_copy_buf_to_user(s, buf, ubuf + tot_written,
		rc = cx18_copy_mdl_to_user(s, mdl, ubuf + tot_written,
				tot_count - tot_written);

		if (buf != &cx->vbi.sliced_mpeg_buf) {
			if (buf->readpos == buf->bytesused)
				cx18_stream_put_buf_fw(s, buf);
		if (mdl != &cx->vbi.sliced_mpeg_mdl) {
			if (mdl->readpos == mdl->bytesused)
				cx18_stream_put_mdl_fw(s, mdl);
			else
				cx18_push(s, buf, &s->q_full);
		} else if (buf->readpos == buf->bytesused) {
				cx18_push(s, mdl, &s->q_full);
		} else if (mdl->readpos == mdl->bytesused) {
			int idx = cx->vbi.inserted_frame % CX18_VBI_FRAMES;

			cx->vbi.sliced_mpeg_size[idx] = 0;
			cx->vbi.inserted_frame++;
			cx->vbi_data_inserted += buf->bytesused;
			cx->vbi_data_inserted += mdl->bytesused;
		}
		if (rc < 0)
			return rc;
+2 −1
Original line number Diff line number Diff line
@@ -910,7 +910,8 @@ static int cx18_log_status(struct file *file, void *fh)
			continue;
		CX18_INFO("Stream %s: status 0x%04lx, %d%% of %d KiB (%d buffers) in use\n",
			  s->name, s->s_flags,
			  atomic_read(&s->q_full.depth) * 100 / s->buffers,
			  atomic_read(&s->q_full.depth) * s->bufs_per_mdl * 100
			   / s->buffers,
			  (s->buffers * s->buf_size) / 1024, s->buffers);
	}
	CX18_INFO("Read MPEG/VBI: %lld/%lld bytes\n",
+41 −17
Original line number Diff line number Diff line
@@ -131,13 +131,39 @@ static void dump_mb(struct cx18 *cx, struct cx18_mailbox *mb, char *name)
 * Functions that run in a work_queue work handling context
 */

static void cx18_mdl_send_to_dvb(struct cx18_stream *s, struct cx18_mdl *mdl)
{
	struct cx18_buffer *buf;

	if (!s->dvb.enabled || mdl->bytesused == 0)
		return;

	/* We ignore mdl and buf readpos accounting here - it doesn't matter */

	/* The likely case */
	if (list_is_singular(&mdl->buf_list)) {
		buf = list_first_entry(&mdl->buf_list, struct cx18_buffer,
				       list);
		if (buf->bytesused)
			dvb_dmx_swfilter(&s->dvb.demux,
					 buf->buf, buf->bytesused);
		return;
	}

	list_for_each_entry(buf, &mdl->buf_list, list) {
		if (buf->bytesused == 0)
			break;
		dvb_dmx_swfilter(&s->dvb.demux, buf->buf, buf->bytesused);
	}
}

static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order)
{
	u32 handle, mdl_ack_count, id;
	struct cx18_mailbox *mb;
	struct cx18_mdl_ack *mdl_ack;
	struct cx18_stream *s;
	struct cx18_buffer *buf;
	struct cx18_mdl *mdl;
	int i;

	mb = &order->mb;
@@ -158,7 +184,7 @@ static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order)
		id = mdl_ack->id;
		/*
		 * Simple integrity check for processing a stale (and possibly
		 * inconsistent mailbox): make sure the buffer id is in the
		 * inconsistent mailbox): make sure the MDL id is in the
		 * valid range for the stream.
		 *
		 * We go through the trouble of dealing with stale mailboxes
@@ -169,44 +195,42 @@ static void epu_dma_done(struct cx18 *cx, struct cx18_in_work_order *order)
		 * There are occasions when we get a half changed mailbox,
		 * which this check catches for a handle & id mismatch.  If the
		 * handle and id do correspond, the worst case is that we
		 * completely lost the old buffer, but pick up the new buffer
		 * completely lost the old MDL, but pick up the new MDL
		 * early (but the new mdl_ack is guaranteed to be good in this
		 * case as the firmware wouldn't point us to a new mdl_ack until
		 * it's filled in).
		 *
		 * cx18_queue_get buf() will detect the lost buffers
		 * cx18_queue_get_mdl() will detect the lost MDLs
		 * and send them back to q_free for fw rotation eventually.
		 */
		if ((order->flags & CX18_F_EWO_MB_STALE_UPON_RECEIPT) &&
		    !(id >= s->mdl_base_idx &&
		      id < (s->mdl_base_idx + s->buffers))) {
			CX18_WARN("Fell behind! Ignoring stale mailbox with "
				  " inconsistent data. Lost buffer for mailbox "
				  " inconsistent data. Lost MDL for mailbox "
				  "seq no %d\n", mb->request);
			break;
		}
		buf = cx18_queue_get_buf(s, id, mdl_ack->data_used);
		mdl = cx18_queue_get_mdl(s, id, mdl_ack->data_used);

		CX18_DEBUG_HI_DMA("DMA DONE for %s (buffer %d)\n", s->name, id);
		if (buf == NULL) {
			CX18_WARN("Could not find buf %d for stream %s\n",
		CX18_DEBUG_HI_DMA("DMA DONE for %s (MDL %d)\n", s->name, id);
		if (mdl == NULL) {
			CX18_WARN("Could not find MDL %d for stream %s\n",
				  id, s->name);
			continue;
		}

		CX18_DEBUG_HI_DMA("%s recv bytesused = %d\n",
				  s->name, buf->bytesused);
				  s->name, mdl->bytesused);

		if (s->type != CX18_ENC_STREAM_TYPE_TS)
			cx18_enqueue(s, buf, &s->q_full);
			cx18_enqueue(s, mdl, &s->q_full);
		else {
			if (s->dvb.enabled)
				dvb_dmx_swfilter(&s->dvb.demux, buf->buf,
						 buf->bytesused);
			cx18_enqueue(s, buf, &s->q_free);
			cx18_mdl_send_to_dvb(s, mdl);
			cx18_enqueue(s, mdl, &s->q_free);
		}
	}
	/* Put as many buffers as possible back into fw use */
	/* Put as many MDLs as possible back into fw use */
	cx18_stream_load_fw_queue(s);

	wake_up(&cx->dma_waitq);
@@ -616,7 +640,7 @@ static int cx18_api_call(struct cx18 *cx, u32 cmd, int args, u32 data[])

	/*
	 * Wait for XPU to perform extra actions for the caller in some cases.
	 * e.g. CX18_CPU_DE_RELEASE_MDL will cause the CPU to send all buffers
	 * e.g. CX18_CPU_DE_RELEASE_MDL will cause the CPU to send all MDLs
	 * back in a burst shortly thereafter
	 */
	if (info->flags & API_SLOW)
Loading