From 40c5520f55924ba87090d0d93222baad74202559 Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Mon, 13 Apr 2009 23:08:00 -0300 Subject: V4L/DVB (11618): cx18: Convert per stream mutex locks to per queue spin locks To avoid sleeps in providing buffers to user space and in handling incoming buffers from the capture unit, converted the per stream mutex for locking queues to 3 spin locks. There is now a spin lock per queue to increase concurrency when moving buffers around. Also simplified queue manipulations and buffer handling of incoming buffers of data from the capture unit. Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx18/cx18-queue.c | 83 +++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 33 deletions(-) (limited to 'drivers/media/video/cx18/cx18-queue.c') diff --git a/drivers/media/video/cx18/cx18-queue.c b/drivers/media/video/cx18/cx18-queue.c index 3046b8e7434..693a745b085 100644 --- a/drivers/media/video/cx18/cx18-queue.c +++ b/drivers/media/video/cx18/cx18-queue.c @@ -53,13 +53,13 @@ struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_buffer *buf, buf->skipped = 0; } - mutex_lock(&s->qlock); - /* q_busy is restricted to a max buffer count imposed by firmware */ if (q == &s->q_busy && atomic_read(&q->buffers) >= CX18_MAX_FW_MDLS_PER_STREAM) q = &s->q_free; + spin_lock(&q->lock); + if (to_front) list_add(&buf->list, &q->list); /* LIFO */ else @@ -67,7 +67,7 @@ struct cx18_queue *_cx18_enqueue(struct cx18_stream *s, struct cx18_buffer *buf, q->bytesused += buf->bytesused - buf->readpos; atomic_inc(&q->buffers); - mutex_unlock(&s->qlock); + spin_unlock(&q->lock); return q; } @@ -75,7 +75,7 @@ struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q) { struct cx18_buffer *buf = NULL; - mutex_lock(&s->qlock); + spin_lock(&q->lock); if (!list_empty(&q->list)) { buf = list_first_entry(&q->list, struct cx18_buffer, list); list_del_init(&buf->list); @@ -83,7 +83,7 @@ struct cx18_buffer *cx18_dequeue(struct cx18_stream *s, struct cx18_queue *q) buf->skipped = 0; atomic_dec(&q->buffers); } - mutex_unlock(&s->qlock); + spin_unlock(&q->lock); return buf; } @@ -94,9 +94,23 @@ struct cx18_buffer *cx18_queue_get_buf(struct cx18_stream *s, u32 id, struct cx18_buffer *buf; struct cx18_buffer *tmp; struct cx18_buffer *ret = NULL; - - mutex_lock(&s->qlock); + LIST_HEAD(sweep_up); + + /* + * We don't have to acquire multiple q locks here, because we are + * serialized by the single threaded work handler. + * Buffers from the firmware will thus remain in order as + * they are moved from q_busy to q_full or to the dvb ring buffer. + */ + spin_lock(&s->q_busy.lock); list_for_each_entry_safe(buf, tmp, &s->q_busy.list, list) { + /* + * We should find what the firmware told us is done, + * right at the front of the queue. If we don't, we likely have + * missed a buffer done message from the firmware. + * Once we skip a buffer repeatedly, relative to the size of + * q_busy, we have high confidence we've missed it. + */ if (buf->id != id) { buf->skipped++; if (buf->skipped >= atomic_read(&s->q_busy.buffers)-1) { @@ -105,38 +119,41 @@ struct cx18_buffer *cx18_queue_get_buf(struct cx18_stream *s, u32 id, "times - it must have dropped out of " "rotation\n", s->name, buf->id, buf->skipped); - /* move it to q_free */ - list_move_tail(&buf->list, &s->q_free.list); - buf->bytesused = buf->readpos = buf->b_flags = - buf->skipped = 0; + /* Sweep it up to put it back into rotation */ + list_move_tail(&buf->list, &sweep_up); atomic_dec(&s->q_busy.buffers); - atomic_inc(&s->q_free.buffers); } continue; } - - buf->bytesused = bytesused; - /* Sync the buffer before we release the qlock */ - cx18_buf_sync_for_cpu(s, buf); - if (s->type == CX18_ENC_STREAM_TYPE_TS) { - /* - * TS doesn't use q_full. As we pull the buffer off of - * the queue here, the caller will have to put it back. - */ - list_del_init(&buf->list); - } else { - /* Move buffer from q_busy to q_full */ - list_move_tail(&buf->list, &s->q_full.list); - set_bit(CX18_F_B_NEED_BUF_SWAP, &buf->b_flags); - s->q_full.bytesused += buf->bytesused; - atomic_inc(&s->q_full.buffers); - } + /* + * We pull the desired buffer off of the queue here. Something + * will have to put it back on a queue later. + */ + list_del_init(&buf->list); atomic_dec(&s->q_busy.buffers); - ret = buf; break; } - mutex_unlock(&s->qlock); + spin_unlock(&s->q_busy.lock); + + /* + * We found the buffer for which we were looking. Get it ready for + * the caller to put on q_full or in the dvb ring buffer. + */ + if (ret != NULL) { + ret->bytesused = bytesused; + ret->skipped = 0; + /* readpos and b_flags were 0'ed when the buf went on q_busy */ + cx18_buf_sync_for_cpu(s, ret); + if (s->type != CX18_ENC_STREAM_TYPE_TS) + set_bit(CX18_F_B_NEED_BUF_SWAP, &ret->b_flags); + } + + /* Put any buffers the firmware is ignoring back into normal rotation */ + list_for_each_entry_safe(buf, tmp, &sweep_up, list) { + list_del_init(&buf->list); + cx18_enqueue(s, buf, &s->q_free); + } return ret; } @@ -148,7 +165,7 @@ static void cx18_queue_flush(struct cx18_stream *s, struct cx18_queue *q) if (q == &s->q_free) return; - mutex_lock(&s->qlock); + spin_lock(&q->lock); while (!list_empty(&q->list)) { buf = list_first_entry(&q->list, struct cx18_buffer, list); list_move_tail(&buf->list, &s->q_free.list); @@ -156,7 +173,7 @@ static void cx18_queue_flush(struct cx18_stream *s, struct cx18_queue *q) atomic_inc(&s->q_free.buffers); } cx18_queue_init(q); - mutex_unlock(&s->qlock); + spin_unlock(&q->lock); } void cx18_flush_queues(struct cx18_stream *s) -- cgit v1.2.3-70-g09d2 From 21a278b85d3c6b8064af0c03aec3205e28aad3b7 Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Wed, 15 Apr 2009 20:45:10 -0300 Subject: V4L/DVB (11619): cx18: Simplify the work handler for outgoing mailbox commands Simplify the way outgoing work handler gets scheduled to send empty buffers back to the firmware for use. Also reduced the memory required for scheduling this outgoing work, by using a single, per stream work object. Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/cx18/cx18-driver.c | 25 ++++------ drivers/media/video/cx18/cx18-driver.h | 30 +----------- drivers/media/video/cx18/cx18-dvb.c | 1 + drivers/media/video/cx18/cx18-queue.c | 2 +- drivers/media/video/cx18/cx18-streams.c | 82 ++------------------------------- drivers/media/video/cx18/cx18-streams.h | 17 +++++-- 6 files changed, 33 insertions(+), 124 deletions(-) (limited to 'drivers/media/video/cx18/cx18-queue.c') diff --git a/drivers/media/video/cx18/cx18-driver.c b/drivers/media/video/cx18/cx18-driver.c index 658cfbb1b97..f0006edc635 100644 --- a/drivers/media/video/cx18/cx18-driver.c +++ b/drivers/media/video/cx18/cx18-driver.c @@ -30,6 +30,7 @@ #include "cx18-irq.h" #include "cx18-gpio.h" #include "cx18-firmware.h" +#include "cx18-queue.h" #include "cx18-streams.h" #include "cx18-av-core.h" #include "cx18-scb.h" @@ -580,13 +581,6 @@ static void __devinit cx18_init_in_work_orders(struct cx18 *cx) } } -static void __devinit cx18_init_out_work_orders(struct cx18 *cx) -{ - int i; - for (i = 0; i < CX18_MAX_OUT_WORK_ORDERS; i++) - INIT_WORK(&cx->out_work_order[i].work, cx18_out_work_handler); -} - /* Precondition: the cx18 structure has been memset to 0. Only the dev and instance fields have been filled in. No assumptions on the card type may be made here (see cx18_init_struct2 @@ -613,7 +607,6 @@ static int __devinit cx18_init_struct1(struct cx18 *cx) return ret; } - cx18_init_out_work_orders(cx); cx18_init_in_work_orders(cx); /* start counting open_id at 1 */ @@ -1103,6 +1096,14 @@ static void cx18_cancel_in_work_orders(struct cx18 *cx) cancel_work_sync(&cx->in_work_order[i].work); } +static void cx18_cancel_out_work_orders(struct cx18 *cx) +{ + int i; + for (i = 0; i < CX18_MAX_STREAMS; i++) + if (&cx->streams[i].video_dev != NULL) + cancel_work_sync(&cx->streams[i].out_work_order); +} + static void cx18_remove(struct pci_dev *pci_dev) { struct v4l2_device *v4l2_dev = pci_get_drvdata(pci_dev); @@ -1121,13 +1122,7 @@ static void cx18_remove(struct pci_dev *pci_dev) /* Incoming work can cause outgoing work, so clean up incoming first */ cx18_cancel_in_work_orders(cx); - - /* - * An outgoing work order can have the only pointer to a dynamically - * allocated buffer, so we need to flush outgoing work and not just - * cancel it, so we don't lose the pointer and leak memory. - */ - flush_workqueue(cx->out_work_queue); + cx18_cancel_out_work_orders(cx); /* Stop ack interrupts that may have been needed for work to finish */ cx18_sw2_irq_disable(cx, IRQ_CPU_TO_EPU_ACK | IRQ_APU_TO_EPU_ACK); diff --git a/drivers/media/video/cx18/cx18-driver.h b/drivers/media/video/cx18/cx18-driver.h index 35a6758a7aa..f89b8236796 100644 --- a/drivers/media/video/cx18/cx18-driver.h +++ b/drivers/media/video/cx18/cx18-driver.h @@ -326,33 +326,6 @@ struct cx18_in_work_order { char *str; }; -/* - * There are 2 types of deferrable tasks that send messages out to the firmware: - * 1. Sending individual buffers back to the firmware - * 2. Sending as many free buffers for a stream from q_free as we can to the fw - * - * The worst case scenario for multiple simultaneous streams is - * TS, YUV, PCM, VBI, MPEG, and IDX all going at once. - * - * We try to load the firmware queue with as many free buffers as possible, - * whenever we get a buffer back for a stream. For the TS we return the single - * buffer to the firmware at that time as well. For all other streams, we - * return single buffers to the firmware as the application drains them. - * - * 6 streams * 2 sets of orders * (1 single buf + 1 load fw from q_free) - * = 24 work orders should cover our needs, provided the applications read - * at a fairly steady rate. If apps don't, we fall back to non-deferred - * operation, when no cx18_out_work_orders are available for use. - */ -#define CX18_MAX_OUT_WORK_ORDERS (24) - -struct cx18_out_work_order { - struct work_struct work; - atomic_t pending; - struct cx18_stream *s; - struct cx18_buffer *buf; /* buf == NULL, means load fw from q_free */ -}; - #define CX18_INVALID_TASK_HANDLE 0xffffffff struct cx18_stream { @@ -381,6 +354,8 @@ struct cx18_stream { struct cx18_queue q_busy; /* busy buffers - in use by firmware */ struct cx18_queue q_full; /* full buffers - data for user apps */ + struct work_struct out_work_order; + /* DVB / Digital Transport */ struct cx18_dvb dvb; }; @@ -603,7 +578,6 @@ struct cx18 { struct workqueue_struct *out_work_queue; char out_workq_name[12]; /* "cx18-NN-out" */ - struct cx18_out_work_order out_work_order[CX18_MAX_OUT_WORK_ORDERS]; /* i2c */ struct i2c_adapter i2c_adap[2]; diff --git a/drivers/media/video/cx18/cx18-dvb.c b/drivers/media/video/cx18/cx18-dvb.c index 3b86f57cd15..e7285a1096f 100644 --- a/drivers/media/video/cx18/cx18-dvb.c +++ b/drivers/media/video/cx18/cx18-dvb.c @@ -23,6 +23,7 @@ #include "cx18-version.h" #include "cx18-dvb.h" #include "cx18-io.h" +#include "cx18-queue.h" #include "cx18-streams.h" #include "cx18-cards.h" #include "s5h1409.h" diff --git a/drivers/media/video/cx18/cx18-queue.c b/drivers/media/video/cx18/cx18-queue.c index 693a745b085..fa1ed7897d9 100644 --- a/drivers/media/video/cx18/cx18-queue.c +++ b/drivers/media/video/cx18/cx18-queue.c @@ -23,8 +23,8 @@ */ #include "cx18-driver.h" -#include "cx18-streams.h" #include "cx18-queue.h" +#include "cx18-streams.h" #include "cx18-scb.h" void cx18_buf_swap(struct cx18_buffer *buf) diff --git a/drivers/media/video/cx18/cx18-streams.c b/drivers/media/video/cx18/cx18-streams.c index e1934e9cfdc..41a1b2618aa 100644 --- a/drivers/media/video/cx18/cx18-streams.c +++ b/drivers/media/video/cx18/cx18-streams.c @@ -124,6 +124,8 @@ static void cx18_stream_init(struct cx18 *cx, int type) cx18_queue_init(&s->q_busy); spin_lock_init(&s->q_full.lock); cx18_queue_init(&s->q_full); + + INIT_WORK(&s->out_work_order, cx18_out_work_handler); } static int cx18_prep_dev(struct cx18 *cx, int type) @@ -477,86 +479,12 @@ void _cx18_stream_load_fw_queue(struct cx18_stream *s) && q == &s->q_busy); } -static inline -void free_out_work_order(struct cx18_out_work_order *order) -{ - atomic_set(&order->pending, 0); -} - void cx18_out_work_handler(struct work_struct *work) { - struct cx18_out_work_order *order = - container_of(work, struct cx18_out_work_order, work); - struct cx18_stream *s = order->s; - struct cx18_buffer *buf = order->buf; - - free_out_work_order(order); - - if (buf == NULL) - _cx18_stream_load_fw_queue(s); - else - _cx18_stream_put_buf_fw(s, buf); -} - -static -struct cx18_out_work_order *alloc_out_work_order(struct cx18 *cx) -{ - int i; - struct cx18_out_work_order *order = NULL; - - for (i = 0; i < CX18_MAX_OUT_WORK_ORDERS; i++) { - /* - * We need "pending" to be atomic to inspect & set its contents - * 1. "pending" is only set to 1 here, but needs multiple access - * protection - * 2. work handler threads only clear "pending" and only - * on one, particular work order at a time, per handler thread. - */ - if (atomic_add_unless(&cx->out_work_order[i].pending, 1, 1)) { - order = &cx->out_work_order[i]; - break; - } - } - return order; -} - -struct cx18_queue *cx18_stream_put_buf_fw(struct cx18_stream *s, - struct cx18_buffer *buf) -{ - struct cx18 *cx = s->cx; - struct cx18_out_work_order *order; - - order = alloc_out_work_order(cx); - if (order == NULL) { - CX18_DEBUG_WARN("No blank, outgoing-mailbox, deferred-work, " - "order forms available; sending buffer %u back " - "to the firmware immediately for stream %s\n", - buf->id, s->name); - return _cx18_stream_put_buf_fw(s, buf); - } - order->s = s; - order->buf = buf; - queue_work(cx->out_work_queue, &order->work); - return NULL; -} - -void cx18_stream_load_fw_queue(struct cx18_stream *s) -{ - struct cx18 *cx = s->cx; - struct cx18_out_work_order *order; + struct cx18_stream *s = + container_of(work, struct cx18_stream, out_work_order); - order = alloc_out_work_order(cx); - if (order == NULL) { - CX18_DEBUG_WARN("No blank, outgoing-mailbox, deferred-work, " - "order forms available; filling the firmware " - "buffer queue immediately for stream %s\n", - s->name); - _cx18_stream_load_fw_queue(s); - return; - } - order->s = s; - order->buf = NULL; /* Indicates to load the fw queue */ - queue_work(cx->out_work_queue, &order->work); + _cx18_stream_load_fw_queue(s); } int cx18_start_v4l2_encode_stream(struct cx18_stream *s) diff --git a/drivers/media/video/cx18/cx18-streams.h b/drivers/media/video/cx18/cx18-streams.h index 1fdcfffb07e..1afc3fd9d82 100644 --- a/drivers/media/video/cx18/cx18-streams.h +++ b/drivers/media/video/cx18/cx18-streams.h @@ -29,9 +29,20 @@ int cx18_streams_register(struct cx18 *cx); void cx18_streams_cleanup(struct cx18 *cx, int unregister); /* Related to submission of buffers to firmware */ -void cx18_stream_load_fw_queue(struct cx18_stream *s); -struct cx18_queue *cx18_stream_put_buf_fw(struct cx18_stream *s, - struct cx18_buffer *buf); +static inline void cx18_stream_load_fw_queue(struct cx18_stream *s) +{ + struct cx18 *cx = s->cx; + queue_work(cx->out_work_queue, &s->out_work_order); +} + +static inline void cx18_stream_put_buf_fw(struct cx18_stream *s, + struct cx18_buffer *buf) +{ + /* Put buf on q_free; the out work handler will move buf(s) to q_busy */ + cx18_enqueue(s, buf, &s->q_free); + cx18_stream_load_fw_queue(s); +} + void cx18_out_work_handler(struct work_struct *work); /* Capture related */ -- cgit v1.2.3-70-g09d2