diff options
Diffstat (limited to 'drivers/media/video/ivtv/ivtv-queue.c')
-rw-r--r-- | drivers/media/video/ivtv/ivtv-queue.c | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/drivers/media/video/ivtv/ivtv-queue.c b/drivers/media/video/ivtv/ivtv-queue.c new file mode 100644 index 000000000000..ccfcef1ad91a --- /dev/null +++ b/drivers/media/video/ivtv/ivtv-queue.c | |||
@@ -0,0 +1,262 @@ | |||
1 | /* | ||
2 | buffer queues. | ||
3 | Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> | ||
4 | Copyright (C) 2004 Chris Kennedy <c@groovy.org> | ||
5 | Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> | ||
6 | |||
7 | This program is free software; you can redistribute it and/or modify | ||
8 | it under the terms of the GNU General Public License as published by | ||
9 | the Free Software Foundation; either version 2 of the License, or | ||
10 | (at your option) any later version. | ||
11 | |||
12 | This program is distributed in the hope that it will be useful, | ||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | GNU General Public License for more details. | ||
16 | |||
17 | You should have received a copy of the GNU General Public License | ||
18 | along with this program; if not, write to the Free Software | ||
19 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
20 | */ | ||
21 | |||
22 | #include "ivtv-driver.h" | ||
23 | #include "ivtv-streams.h" | ||
24 | #include "ivtv-queue.h" | ||
25 | #include "ivtv-mailbox.h" | ||
26 | |||
27 | int ivtv_buf_copy_from_user(struct ivtv_stream *s, struct ivtv_buffer *buf, const char __user *src, int copybytes) | ||
28 | { | ||
29 | if (s->buf_size - buf->bytesused < copybytes) | ||
30 | copybytes = s->buf_size - buf->bytesused; | ||
31 | if (copy_from_user(buf->buf + buf->bytesused, src, copybytes)) { | ||
32 | return -EFAULT; | ||
33 | } | ||
34 | buf->bytesused += copybytes; | ||
35 | return copybytes; | ||
36 | } | ||
37 | |||
38 | void ivtv_buf_swap(struct ivtv_buffer *buf) | ||
39 | { | ||
40 | int i; | ||
41 | |||
42 | for (i = 0; i < buf->bytesused; i += 4) | ||
43 | swab32s((u32 *)(buf->buf + i)); | ||
44 | } | ||
45 | |||
46 | void ivtv_queue_init(struct ivtv_queue *q) | ||
47 | { | ||
48 | INIT_LIST_HEAD(&q->list); | ||
49 | q->buffers = 0; | ||
50 | q->length = 0; | ||
51 | q->bytesused = 0; | ||
52 | } | ||
53 | |||
54 | void ivtv_enqueue(struct ivtv_stream *s, struct ivtv_buffer *buf, struct ivtv_queue *q) | ||
55 | { | ||
56 | unsigned long flags = 0; | ||
57 | |||
58 | /* clear the buffer if it is going to be enqueued to the free queue */ | ||
59 | if (q == &s->q_free) { | ||
60 | buf->bytesused = 0; | ||
61 | buf->readpos = 0; | ||
62 | buf->b_flags = 0; | ||
63 | } | ||
64 | spin_lock_irqsave(&s->qlock, flags); | ||
65 | list_add_tail(&buf->list, &q->list); | ||
66 | q->buffers++; | ||
67 | q->length += s->buf_size; | ||
68 | q->bytesused += buf->bytesused - buf->readpos; | ||
69 | spin_unlock_irqrestore(&s->qlock, flags); | ||
70 | } | ||
71 | |||
72 | struct ivtv_buffer *ivtv_dequeue(struct ivtv_stream *s, struct ivtv_queue *q) | ||
73 | { | ||
74 | struct ivtv_buffer *buf = NULL; | ||
75 | unsigned long flags = 0; | ||
76 | |||
77 | spin_lock_irqsave(&s->qlock, flags); | ||
78 | if (!list_empty(&q->list)) { | ||
79 | buf = list_entry(q->list.next, struct ivtv_buffer, list); | ||
80 | list_del_init(q->list.next); | ||
81 | q->buffers--; | ||
82 | q->length -= s->buf_size; | ||
83 | q->bytesused -= buf->bytesused - buf->readpos; | ||
84 | } | ||
85 | spin_unlock_irqrestore(&s->qlock, flags); | ||
86 | return buf; | ||
87 | } | ||
88 | |||
89 | static void ivtv_queue_move_buf(struct ivtv_stream *s, struct ivtv_queue *from, | ||
90 | struct ivtv_queue *to, int clear, int full) | ||
91 | { | ||
92 | struct ivtv_buffer *buf = list_entry(from->list.next, struct ivtv_buffer, list); | ||
93 | |||
94 | list_move_tail(from->list.next, &to->list); | ||
95 | from->buffers--; | ||
96 | from->length -= s->buf_size; | ||
97 | from->bytesused -= buf->bytesused - buf->readpos; | ||
98 | /* special handling for q_free */ | ||
99 | if (clear) | ||
100 | buf->bytesused = buf->readpos = buf->b_flags = 0; | ||
101 | else if (full) { | ||
102 | /* special handling for stolen buffers, assume | ||
103 | all bytes are used. */ | ||
104 | buf->bytesused = s->buf_size; | ||
105 | buf->readpos = buf->b_flags = 0; | ||
106 | } | ||
107 | to->buffers++; | ||
108 | to->length += s->buf_size; | ||
109 | to->bytesused += buf->bytesused - buf->readpos; | ||
110 | } | ||
111 | |||
112 | /* Move 'needed_bytes' worth of buffers from queue 'from' into queue 'to'. | ||
113 | If 'needed_bytes' == 0, then move all buffers from 'from' into 'to'. | ||
114 | If 'steal' != NULL, then buffers may also taken from that queue if | ||
115 | needed. | ||
116 | |||
117 | The buffer is automatically cleared if it goes to the free queue. It is | ||
118 | also cleared if buffers need to be taken from the 'steal' queue and | ||
119 | the 'from' queue is the free queue. | ||
120 | |||
121 | When 'from' is q_free, then needed_bytes is compared to the total | ||
122 | available buffer length, otherwise needed_bytes is compared to the | ||
123 | bytesused value. For the 'steal' queue the total available buffer | ||
124 | length is always used. | ||
125 | |||
126 | -ENOMEM is returned if the buffers could not be obtained, 0 if all | ||
127 | buffers where obtained from the 'from' list and if non-zero then | ||
128 | the number of stolen buffers is returned. */ | ||
129 | int ivtv_queue_move(struct ivtv_stream *s, struct ivtv_queue *from, struct ivtv_queue *steal, | ||
130 | struct ivtv_queue *to, int needed_bytes) | ||
131 | { | ||
132 | unsigned long flags; | ||
133 | int rc = 0; | ||
134 | int from_free = from == &s->q_free; | ||
135 | int to_free = to == &s->q_free; | ||
136 | int bytes_available; | ||
137 | |||
138 | spin_lock_irqsave(&s->qlock, flags); | ||
139 | if (needed_bytes == 0) { | ||
140 | from_free = 1; | ||
141 | needed_bytes = from->length; | ||
142 | } | ||
143 | |||
144 | bytes_available = from_free ? from->length : from->bytesused; | ||
145 | bytes_available += steal ? steal->length : 0; | ||
146 | |||
147 | if (bytes_available < needed_bytes) { | ||
148 | spin_unlock_irqrestore(&s->qlock, flags); | ||
149 | return -ENOMEM; | ||
150 | } | ||
151 | if (from_free) { | ||
152 | u32 old_length = to->length; | ||
153 | |||
154 | while (to->length - old_length < needed_bytes) { | ||
155 | if (list_empty(&from->list)) | ||
156 | from = steal; | ||
157 | if (from == steal) | ||
158 | rc++; /* keep track of 'stolen' buffers */ | ||
159 | ivtv_queue_move_buf(s, from, to, 1, 0); | ||
160 | } | ||
161 | } | ||
162 | else { | ||
163 | u32 old_bytesused = to->bytesused; | ||
164 | |||
165 | while (to->bytesused - old_bytesused < needed_bytes) { | ||
166 | if (list_empty(&from->list)) | ||
167 | from = steal; | ||
168 | if (from == steal) | ||
169 | rc++; /* keep track of 'stolen' buffers */ | ||
170 | ivtv_queue_move_buf(s, from, to, to_free, rc); | ||
171 | } | ||
172 | } | ||
173 | spin_unlock_irqrestore(&s->qlock, flags); | ||
174 | return rc; | ||
175 | } | ||
176 | |||
177 | void ivtv_flush_queues(struct ivtv_stream *s) | ||
178 | { | ||
179 | ivtv_queue_move(s, &s->q_io, NULL, &s->q_free, 0); | ||
180 | ivtv_queue_move(s, &s->q_full, NULL, &s->q_free, 0); | ||
181 | ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0); | ||
182 | ivtv_queue_move(s, &s->q_predma, NULL, &s->q_free, 0); | ||
183 | } | ||
184 | |||
185 | int ivtv_stream_alloc(struct ivtv_stream *s) | ||
186 | { | ||
187 | struct ivtv *itv = s->itv; | ||
188 | int SGsize = sizeof(struct ivtv_SG_element) * s->buffers; | ||
189 | int i; | ||
190 | |||
191 | if (s->buffers == 0) | ||
192 | return 0; | ||
193 | |||
194 | IVTV_DEBUG_INFO("Allocate %s%s stream: %d x %d buffers (%dkB total)\n", | ||
195 | s->dma != PCI_DMA_NONE ? "DMA " : "", | ||
196 | s->name, s->buffers, s->buf_size, s->buffers * s->buf_size / 1024); | ||
197 | |||
198 | /* Allocate DMA SG Arrays */ | ||
199 | if (s->dma != PCI_DMA_NONE) { | ||
200 | s->SGarray = (struct ivtv_SG_element *)kzalloc(SGsize, GFP_KERNEL); | ||
201 | if (s->SGarray == NULL) { | ||
202 | IVTV_ERR("Could not allocate SGarray for %s stream\n", s->name); | ||
203 | return -ENOMEM; | ||
204 | } | ||
205 | s->SG_length = 0; | ||
206 | s->SG_handle = pci_map_single(itv->dev, s->SGarray, SGsize, s->dma); | ||
207 | ivtv_stream_sync_for_cpu(s); | ||
208 | } | ||
209 | |||
210 | /* allocate stream buffers. Initially all buffers are in q_free. */ | ||
211 | for (i = 0; i < s->buffers; i++) { | ||
212 | struct ivtv_buffer *buf = kzalloc(sizeof(struct ivtv_buffer), GFP_KERNEL); | ||
213 | |||
214 | if (buf == NULL) | ||
215 | break; | ||
216 | buf->buf = kmalloc(s->buf_size + 256, GFP_KERNEL); | ||
217 | if (buf->buf == NULL) { | ||
218 | kfree(buf); | ||
219 | break; | ||
220 | } | ||
221 | INIT_LIST_HEAD(&buf->list); | ||
222 | if (s->dma != PCI_DMA_NONE) { | ||
223 | buf->dma_handle = pci_map_single(s->itv->dev, | ||
224 | buf->buf, s->buf_size + 256, s->dma); | ||
225 | ivtv_buf_sync_for_cpu(s, buf); | ||
226 | } | ||
227 | ivtv_enqueue(s, buf, &s->q_free); | ||
228 | } | ||
229 | if (i == s->buffers) | ||
230 | return 0; | ||
231 | IVTV_ERR("Couldn't allocate buffers for %s stream\n", s->name); | ||
232 | ivtv_stream_free(s); | ||
233 | return -ENOMEM; | ||
234 | } | ||
235 | |||
236 | void ivtv_stream_free(struct ivtv_stream *s) | ||
237 | { | ||
238 | struct ivtv_buffer *buf; | ||
239 | |||
240 | /* move all buffers to q_free */ | ||
241 | ivtv_flush_queues(s); | ||
242 | |||
243 | /* empty q_free */ | ||
244 | while ((buf = ivtv_dequeue(s, &s->q_free))) { | ||
245 | if (s->dma != PCI_DMA_NONE) | ||
246 | pci_unmap_single(s->itv->dev, buf->dma_handle, | ||
247 | s->buf_size + 256, s->dma); | ||
248 | kfree(buf->buf); | ||
249 | kfree(buf); | ||
250 | } | ||
251 | |||
252 | /* Free SG Array/Lists */ | ||
253 | if (s->SGarray != NULL) { | ||
254 | if (s->SG_handle != IVTV_DMA_UNMAPPED) { | ||
255 | pci_unmap_single(s->itv->dev, s->SG_handle, | ||
256 | sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE); | ||
257 | s->SG_handle = IVTV_DMA_UNMAPPED; | ||
258 | } | ||
259 | s->SGarray = NULL; | ||
260 | s->SG_length = 0; | ||
261 | } | ||
262 | } | ||