162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci buffer queues. 462306a36Sopenharmony_ci Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> 562306a36Sopenharmony_ci Copyright (C) 2004 Chris Kennedy <c@groovy.org> 662306a36Sopenharmony_ci Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include "ivtv-driver.h" 1162306a36Sopenharmony_ci#include "ivtv-queue.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ciint ivtv_buf_copy_from_user(struct ivtv_stream *s, struct ivtv_buffer *buf, const char __user *src, int copybytes) 1462306a36Sopenharmony_ci{ 1562306a36Sopenharmony_ci if (s->buf_size - buf->bytesused < copybytes) 1662306a36Sopenharmony_ci copybytes = s->buf_size - buf->bytesused; 1762306a36Sopenharmony_ci if (copy_from_user(buf->buf + buf->bytesused, src, copybytes)) { 1862306a36Sopenharmony_ci return -EFAULT; 1962306a36Sopenharmony_ci } 2062306a36Sopenharmony_ci buf->bytesused += copybytes; 2162306a36Sopenharmony_ci return copybytes; 2262306a36Sopenharmony_ci} 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_civoid ivtv_buf_swap(struct ivtv_buffer *buf) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci int i; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci for (i = 0; i < buf->bytesused; i += 4) 2962306a36Sopenharmony_ci swab32s((u32 *)(buf->buf + i)); 3062306a36Sopenharmony_ci} 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_civoid ivtv_queue_init(struct ivtv_queue *q) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci INIT_LIST_HEAD(&q->list); 3562306a36Sopenharmony_ci q->buffers = 0; 3662306a36Sopenharmony_ci q->length = 0; 3762306a36Sopenharmony_ci q->bytesused = 0; 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_civoid ivtv_enqueue(struct ivtv_stream *s, struct ivtv_buffer *buf, struct ivtv_queue *q) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci unsigned long flags; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci /* clear the buffer if it is going to be enqueued to the free queue */ 4562306a36Sopenharmony_ci if (q == &s->q_free) { 4662306a36Sopenharmony_ci buf->bytesused = 0; 4762306a36Sopenharmony_ci buf->readpos = 0; 4862306a36Sopenharmony_ci buf->b_flags = 0; 4962306a36Sopenharmony_ci buf->dma_xfer_cnt = 0; 5062306a36Sopenharmony_ci } 5162306a36Sopenharmony_ci spin_lock_irqsave(&s->qlock, flags); 5262306a36Sopenharmony_ci list_add_tail(&buf->list, &q->list); 5362306a36Sopenharmony_ci q->buffers++; 5462306a36Sopenharmony_ci q->length += s->buf_size; 5562306a36Sopenharmony_ci q->bytesused += buf->bytesused - buf->readpos; 5662306a36Sopenharmony_ci spin_unlock_irqrestore(&s->qlock, flags); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistruct ivtv_buffer *ivtv_dequeue(struct ivtv_stream *s, struct ivtv_queue *q) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct ivtv_buffer *buf = NULL; 6262306a36Sopenharmony_ci unsigned long flags; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci spin_lock_irqsave(&s->qlock, flags); 6562306a36Sopenharmony_ci if (!list_empty(&q->list)) { 6662306a36Sopenharmony_ci buf = list_entry(q->list.next, struct ivtv_buffer, list); 6762306a36Sopenharmony_ci list_del_init(q->list.next); 6862306a36Sopenharmony_ci q->buffers--; 6962306a36Sopenharmony_ci q->length -= s->buf_size; 7062306a36Sopenharmony_ci q->bytesused -= buf->bytesused - buf->readpos; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci spin_unlock_irqrestore(&s->qlock, flags); 7362306a36Sopenharmony_ci return buf; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic void ivtv_queue_move_buf(struct ivtv_stream *s, struct ivtv_queue *from, 7762306a36Sopenharmony_ci struct ivtv_queue *to, int clear) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci struct ivtv_buffer *buf = list_entry(from->list.next, struct ivtv_buffer, list); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci list_move_tail(from->list.next, &to->list); 8262306a36Sopenharmony_ci from->buffers--; 8362306a36Sopenharmony_ci from->length -= s->buf_size; 8462306a36Sopenharmony_ci from->bytesused -= buf->bytesused - buf->readpos; 8562306a36Sopenharmony_ci /* special handling for q_free */ 8662306a36Sopenharmony_ci if (clear) 8762306a36Sopenharmony_ci buf->bytesused = buf->readpos = buf->b_flags = buf->dma_xfer_cnt = 0; 8862306a36Sopenharmony_ci to->buffers++; 8962306a36Sopenharmony_ci to->length += s->buf_size; 9062306a36Sopenharmony_ci to->bytesused += buf->bytesused - buf->readpos; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* Move 'needed_bytes' worth of buffers from queue 'from' into queue 'to'. 9462306a36Sopenharmony_ci If 'needed_bytes' == 0, then move all buffers from 'from' into 'to'. 9562306a36Sopenharmony_ci If 'steal' != NULL, then buffers may also taken from that queue if 9662306a36Sopenharmony_ci needed, but only if 'from' is the free queue. 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci The buffer is automatically cleared if it goes to the free queue. It is 9962306a36Sopenharmony_ci also cleared if buffers need to be taken from the 'steal' queue and 10062306a36Sopenharmony_ci the 'from' queue is the free queue. 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci When 'from' is q_free, then needed_bytes is compared to the total 10362306a36Sopenharmony_ci available buffer length, otherwise needed_bytes is compared to the 10462306a36Sopenharmony_ci bytesused value. For the 'steal' queue the total available buffer 10562306a36Sopenharmony_ci length is always used. 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci -ENOMEM is returned if the buffers could not be obtained, 0 if all 10862306a36Sopenharmony_ci buffers where obtained from the 'from' list and if non-zero then 10962306a36Sopenharmony_ci the number of stolen buffers is returned. */ 11062306a36Sopenharmony_ciint ivtv_queue_move(struct ivtv_stream *s, struct ivtv_queue *from, struct ivtv_queue *steal, 11162306a36Sopenharmony_ci struct ivtv_queue *to, int needed_bytes) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci unsigned long flags; 11462306a36Sopenharmony_ci int rc = 0; 11562306a36Sopenharmony_ci int from_free = from == &s->q_free; 11662306a36Sopenharmony_ci int to_free = to == &s->q_free; 11762306a36Sopenharmony_ci int bytes_available, bytes_steal; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci spin_lock_irqsave(&s->qlock, flags); 12062306a36Sopenharmony_ci if (needed_bytes == 0) { 12162306a36Sopenharmony_ci from_free = 1; 12262306a36Sopenharmony_ci needed_bytes = from->length; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci bytes_available = from_free ? from->length : from->bytesused; 12662306a36Sopenharmony_ci bytes_steal = (from_free && steal) ? steal->length : 0; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (bytes_available + bytes_steal < needed_bytes) { 12962306a36Sopenharmony_ci spin_unlock_irqrestore(&s->qlock, flags); 13062306a36Sopenharmony_ci return -ENOMEM; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci while (steal && bytes_available < needed_bytes) { 13362306a36Sopenharmony_ci struct ivtv_buffer *buf = list_entry(steal->list.prev, struct ivtv_buffer, list); 13462306a36Sopenharmony_ci u16 dma_xfer_cnt = buf->dma_xfer_cnt; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci /* move buffers from the tail of the 'steal' queue to the tail of the 13762306a36Sopenharmony_ci 'from' queue. Always copy all the buffers with the same dma_xfer_cnt 13862306a36Sopenharmony_ci value, this ensures that you do not end up with partial frame data 13962306a36Sopenharmony_ci if one frame is stored in multiple buffers. */ 14062306a36Sopenharmony_ci while (dma_xfer_cnt == buf->dma_xfer_cnt) { 14162306a36Sopenharmony_ci list_move_tail(steal->list.prev, &from->list); 14262306a36Sopenharmony_ci rc++; 14362306a36Sopenharmony_ci steal->buffers--; 14462306a36Sopenharmony_ci steal->length -= s->buf_size; 14562306a36Sopenharmony_ci steal->bytesused -= buf->bytesused - buf->readpos; 14662306a36Sopenharmony_ci buf->bytesused = buf->readpos = buf->b_flags = buf->dma_xfer_cnt = 0; 14762306a36Sopenharmony_ci from->buffers++; 14862306a36Sopenharmony_ci from->length += s->buf_size; 14962306a36Sopenharmony_ci bytes_available += s->buf_size; 15062306a36Sopenharmony_ci if (list_empty(&steal->list)) 15162306a36Sopenharmony_ci break; 15262306a36Sopenharmony_ci buf = list_entry(steal->list.prev, struct ivtv_buffer, list); 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci if (from_free) { 15662306a36Sopenharmony_ci u32 old_length = to->length; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci while (to->length - old_length < needed_bytes) { 15962306a36Sopenharmony_ci ivtv_queue_move_buf(s, from, to, 1); 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci else { 16362306a36Sopenharmony_ci u32 old_bytesused = to->bytesused; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci while (to->bytesused - old_bytesused < needed_bytes) { 16662306a36Sopenharmony_ci ivtv_queue_move_buf(s, from, to, to_free); 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci spin_unlock_irqrestore(&s->qlock, flags); 17062306a36Sopenharmony_ci return rc; 17162306a36Sopenharmony_ci} 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_civoid ivtv_flush_queues(struct ivtv_stream *s) 17462306a36Sopenharmony_ci{ 17562306a36Sopenharmony_ci ivtv_queue_move(s, &s->q_io, NULL, &s->q_free, 0); 17662306a36Sopenharmony_ci ivtv_queue_move(s, &s->q_full, NULL, &s->q_free, 0); 17762306a36Sopenharmony_ci ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0); 17862306a36Sopenharmony_ci ivtv_queue_move(s, &s->q_predma, NULL, &s->q_free, 0); 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ciint ivtv_stream_alloc(struct ivtv_stream *s) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci struct ivtv *itv = s->itv; 18462306a36Sopenharmony_ci int SGsize = sizeof(struct ivtv_sg_host_element) * s->buffers; 18562306a36Sopenharmony_ci int i; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci if (s->buffers == 0) 18862306a36Sopenharmony_ci return 0; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci IVTV_DEBUG_INFO("Allocate %s%s stream: %d x %d buffers (%dkB total)\n", 19162306a36Sopenharmony_ci s->dma != DMA_NONE ? "DMA " : "", 19262306a36Sopenharmony_ci s->name, s->buffers, s->buf_size, s->buffers * s->buf_size / 1024); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci s->sg_pending = kzalloc(SGsize, GFP_KERNEL|__GFP_NOWARN); 19562306a36Sopenharmony_ci if (s->sg_pending == NULL) { 19662306a36Sopenharmony_ci IVTV_ERR("Could not allocate sg_pending for %s stream\n", s->name); 19762306a36Sopenharmony_ci return -ENOMEM; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci s->sg_pending_size = 0; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci s->sg_processing = kzalloc(SGsize, GFP_KERNEL|__GFP_NOWARN); 20262306a36Sopenharmony_ci if (s->sg_processing == NULL) { 20362306a36Sopenharmony_ci IVTV_ERR("Could not allocate sg_processing for %s stream\n", s->name); 20462306a36Sopenharmony_ci kfree(s->sg_pending); 20562306a36Sopenharmony_ci s->sg_pending = NULL; 20662306a36Sopenharmony_ci return -ENOMEM; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci s->sg_processing_size = 0; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci s->sg_dma = kzalloc(sizeof(struct ivtv_sg_element), 21162306a36Sopenharmony_ci GFP_KERNEL|__GFP_NOWARN); 21262306a36Sopenharmony_ci if (s->sg_dma == NULL) { 21362306a36Sopenharmony_ci IVTV_ERR("Could not allocate sg_dma for %s stream\n", s->name); 21462306a36Sopenharmony_ci kfree(s->sg_pending); 21562306a36Sopenharmony_ci s->sg_pending = NULL; 21662306a36Sopenharmony_ci kfree(s->sg_processing); 21762306a36Sopenharmony_ci s->sg_processing = NULL; 21862306a36Sopenharmony_ci return -ENOMEM; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci if (ivtv_might_use_dma(s)) { 22162306a36Sopenharmony_ci s->sg_handle = dma_map_single(&itv->pdev->dev, s->sg_dma, 22262306a36Sopenharmony_ci sizeof(struct ivtv_sg_element), 22362306a36Sopenharmony_ci DMA_TO_DEVICE); 22462306a36Sopenharmony_ci ivtv_stream_sync_for_cpu(s); 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci /* allocate stream buffers. Initially all buffers are in q_free. */ 22862306a36Sopenharmony_ci for (i = 0; i < s->buffers; i++) { 22962306a36Sopenharmony_ci struct ivtv_buffer *buf = kzalloc(sizeof(struct ivtv_buffer), 23062306a36Sopenharmony_ci GFP_KERNEL|__GFP_NOWARN); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (buf == NULL) 23362306a36Sopenharmony_ci break; 23462306a36Sopenharmony_ci buf->buf = kmalloc(s->buf_size + 256, GFP_KERNEL|__GFP_NOWARN); 23562306a36Sopenharmony_ci if (buf->buf == NULL) { 23662306a36Sopenharmony_ci kfree(buf); 23762306a36Sopenharmony_ci break; 23862306a36Sopenharmony_ci } 23962306a36Sopenharmony_ci INIT_LIST_HEAD(&buf->list); 24062306a36Sopenharmony_ci if (ivtv_might_use_dma(s)) { 24162306a36Sopenharmony_ci buf->dma_handle = dma_map_single(&s->itv->pdev->dev, 24262306a36Sopenharmony_ci buf->buf, s->buf_size + 256, s->dma); 24362306a36Sopenharmony_ci ivtv_buf_sync_for_cpu(s, buf); 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci ivtv_enqueue(s, buf, &s->q_free); 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci if (i == s->buffers) 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci IVTV_ERR("Couldn't allocate buffers for %s stream\n", s->name); 25062306a36Sopenharmony_ci ivtv_stream_free(s); 25162306a36Sopenharmony_ci return -ENOMEM; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_civoid ivtv_stream_free(struct ivtv_stream *s) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci struct ivtv_buffer *buf; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci /* move all buffers to q_free */ 25962306a36Sopenharmony_ci ivtv_flush_queues(s); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci /* empty q_free */ 26262306a36Sopenharmony_ci while ((buf = ivtv_dequeue(s, &s->q_free))) { 26362306a36Sopenharmony_ci if (ivtv_might_use_dma(s)) 26462306a36Sopenharmony_ci dma_unmap_single(&s->itv->pdev->dev, buf->dma_handle, 26562306a36Sopenharmony_ci s->buf_size + 256, s->dma); 26662306a36Sopenharmony_ci kfree(buf->buf); 26762306a36Sopenharmony_ci kfree(buf); 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci /* Free SG Array/Lists */ 27162306a36Sopenharmony_ci if (s->sg_dma != NULL) { 27262306a36Sopenharmony_ci if (s->sg_handle != IVTV_DMA_UNMAPPED) { 27362306a36Sopenharmony_ci dma_unmap_single(&s->itv->pdev->dev, s->sg_handle, 27462306a36Sopenharmony_ci sizeof(struct ivtv_sg_element), 27562306a36Sopenharmony_ci DMA_TO_DEVICE); 27662306a36Sopenharmony_ci s->sg_handle = IVTV_DMA_UNMAPPED; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci kfree(s->sg_pending); 27962306a36Sopenharmony_ci kfree(s->sg_processing); 28062306a36Sopenharmony_ci kfree(s->sg_dma); 28162306a36Sopenharmony_ci s->sg_pending = NULL; 28262306a36Sopenharmony_ci s->sg_processing = NULL; 28362306a36Sopenharmony_ci s->sg_dma = NULL; 28462306a36Sopenharmony_ci s->sg_pending_size = 0; 28562306a36Sopenharmony_ci s->sg_processing_size = 0; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci} 288