regmap: rbtree: Fixed node range check on sync
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / comedi_buf.c
1 /*
2  * comedi_buf.c
3  *
4  * COMEDI - Linux Control and Measurement Device Interface
5  * Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
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., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21
22 #include "comedidev.h"
23 #include "comedi_internal.h"
24
25 #ifdef PAGE_KERNEL_NOCACHE
26 #define COMEDI_PAGE_PROTECTION          PAGE_KERNEL_NOCACHE
27 #else
28 #define COMEDI_PAGE_PROTECTION          PAGE_KERNEL
29 #endif
30
31 static void __comedi_buf_free(struct comedi_device *dev,
32                               struct comedi_subdevice *s,
33                               unsigned n_pages)
34 {
35         struct comedi_async *async = s->async;
36         struct comedi_buf_page *buf;
37         unsigned i;
38
39         if (async->prealloc_buf) {
40                 vunmap(async->prealloc_buf);
41                 async->prealloc_buf = NULL;
42                 async->prealloc_bufsz = 0;
43         }
44
45         if (!async->buf_page_list)
46                 return;
47
48         for (i = 0; i < n_pages; ++i) {
49                 buf = &async->buf_page_list[i];
50                 if (buf->virt_addr) {
51                         clear_bit(PG_reserved,
52                                   &(virt_to_page(buf->virt_addr)->flags));
53                         if (s->async_dma_dir != DMA_NONE) {
54                                 dma_free_coherent(dev->hw_dev,
55                                                   PAGE_SIZE,
56                                                   buf->virt_addr,
57                                                   buf->dma_addr);
58                         } else {
59                                 free_page((unsigned long)buf->virt_addr);
60                         }
61                 }
62         }
63         vfree(async->buf_page_list);
64         async->buf_page_list = NULL;
65         async->n_buf_pages = 0;
66 }
67
68 static void __comedi_buf_alloc(struct comedi_device *dev,
69                                struct comedi_subdevice *s,
70                                unsigned n_pages)
71 {
72         struct comedi_async *async = s->async;
73         struct page **pages = NULL;
74         struct comedi_buf_page *buf;
75         unsigned i;
76
77         async->buf_page_list = vzalloc(sizeof(*buf) * n_pages);
78         if (async->buf_page_list)
79                 pages = vmalloc(sizeof(struct page *) * n_pages);
80
81         if (!pages)
82                 return;
83
84         for (i = 0; i < n_pages; i++) {
85                 buf = &async->buf_page_list[i];
86                 if (s->async_dma_dir != DMA_NONE)
87                         buf->virt_addr = dma_alloc_coherent(dev->hw_dev,
88                                                             PAGE_SIZE,
89                                                             &buf->dma_addr,
90                                                             GFP_KERNEL |
91                                                             __GFP_COMP);
92                 else
93                         buf->virt_addr = (void *)get_zeroed_page(GFP_KERNEL);
94                 if (!buf->virt_addr)
95                         break;
96
97                 set_bit(PG_reserved, &(virt_to_page(buf->virt_addr)->flags));
98
99                 pages[i] = virt_to_page(buf->virt_addr);
100         }
101
102         /* vmap the prealloc_buf if all the pages were allocated */
103         if (i == n_pages)
104                 async->prealloc_buf = vmap(pages, n_pages, VM_MAP,
105                                            COMEDI_PAGE_PROTECTION);
106
107         vfree(pages);
108 }
109
110 int comedi_buf_alloc(struct comedi_device *dev, struct comedi_subdevice *s,
111                      unsigned long new_size)
112 {
113         struct comedi_async *async = s->async;
114
115         /* Round up new_size to multiple of PAGE_SIZE */
116         new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK;
117
118         /* if no change is required, do nothing */
119         if (async->prealloc_buf && async->prealloc_bufsz == new_size)
120                 return 0;
121
122         /* deallocate old buffer */
123         __comedi_buf_free(dev, s, async->n_buf_pages);
124
125         /* allocate new buffer */
126         if (new_size) {
127                 unsigned n_pages = new_size >> PAGE_SHIFT;
128
129                 __comedi_buf_alloc(dev, s, n_pages);
130
131                 if (!async->prealloc_buf) {
132                         /* allocation failed */
133                         __comedi_buf_free(dev, s, n_pages);
134                         return -ENOMEM;
135                 }
136                 async->n_buf_pages = n_pages;
137         }
138         async->prealloc_bufsz = new_size;
139
140         return 0;
141 }
142
143 void comedi_buf_reset(struct comedi_async *async)
144 {
145         async->buf_write_alloc_count = 0;
146         async->buf_write_count = 0;
147         async->buf_read_alloc_count = 0;
148         async->buf_read_count = 0;
149
150         async->buf_write_ptr = 0;
151         async->buf_read_ptr = 0;
152
153         async->cur_chan = 0;
154         async->scan_progress = 0;
155         async->munge_chan = 0;
156         async->munge_count = 0;
157         async->munge_ptr = 0;
158
159         async->events = 0;
160 }
161
162 static unsigned int comedi_buf_write_n_available(struct comedi_async *async)
163 {
164         unsigned int free_end = async->buf_read_count + async->prealloc_bufsz;
165
166         return free_end - async->buf_write_alloc_count;
167 }
168
169 static unsigned int __comedi_buf_write_alloc(struct comedi_async *async,
170                                              unsigned int nbytes,
171                                              int strict)
172 {
173         unsigned int available = comedi_buf_write_n_available(async);
174
175         if (nbytes > available)
176                 nbytes = strict ? 0 : available;
177
178         async->buf_write_alloc_count += nbytes;
179
180         /*
181          * ensure the async buffer 'counts' are read and updated
182          * before we write data to the write-alloc'ed buffer space
183          */
184         smp_mb();
185
186         return nbytes;
187 }
188
189 /* allocates chunk for the writer from free buffer space */
190 unsigned int comedi_buf_write_alloc(struct comedi_async *async,
191                                     unsigned int nbytes)
192 {
193         return __comedi_buf_write_alloc(async, nbytes, 0);
194 }
195 EXPORT_SYMBOL_GPL(comedi_buf_write_alloc);
196
197 /*
198  * munging is applied to data by core as it passes between user
199  * and kernel space
200  */
201 static unsigned int comedi_buf_munge(struct comedi_async *async,
202                                      unsigned int num_bytes)
203 {
204         struct comedi_subdevice *s = async->subdevice;
205         unsigned int count = 0;
206         const unsigned num_sample_bytes = bytes_per_sample(s);
207
208         if (!s->munge || (async->cmd.flags & CMDF_RAWDATA)) {
209                 async->munge_count += num_bytes;
210                 count = num_bytes;
211         } else {
212                 /* don't munge partial samples */
213                 num_bytes -= num_bytes % num_sample_bytes;
214                 while (count < num_bytes) {
215                         int block_size = num_bytes - count;
216                         unsigned int buf_end;
217
218                         buf_end = async->prealloc_bufsz - async->munge_ptr;
219                         if (block_size > buf_end)
220                                 block_size = buf_end;
221
222                         s->munge(s->device, s,
223                                  async->prealloc_buf + async->munge_ptr,
224                                  block_size, async->munge_chan);
225
226                         /*
227                          * ensure data is munged in buffer before the
228                          * async buffer munge_count is incremented
229                          */
230                         smp_wmb();
231
232                         async->munge_chan += block_size / num_sample_bytes;
233                         async->munge_chan %= async->cmd.chanlist_len;
234                         async->munge_count += block_size;
235                         async->munge_ptr += block_size;
236                         async->munge_ptr %= async->prealloc_bufsz;
237                         count += block_size;
238                 }
239         }
240
241         return count;
242 }
243
244 unsigned int comedi_buf_write_n_allocated(struct comedi_async *async)
245 {
246         return async->buf_write_alloc_count - async->buf_write_count;
247 }
248
249 /* transfers a chunk from writer to filled buffer space */
250 unsigned int comedi_buf_write_free(struct comedi_async *async,
251                                    unsigned int nbytes)
252 {
253         unsigned int allocated = comedi_buf_write_n_allocated(async);
254
255         if (nbytes > allocated)
256                 nbytes = allocated;
257
258         async->buf_write_count += nbytes;
259         async->buf_write_ptr += nbytes;
260         comedi_buf_munge(async, async->buf_write_count - async->munge_count);
261         if (async->buf_write_ptr >= async->prealloc_bufsz)
262                 async->buf_write_ptr %= async->prealloc_bufsz;
263
264         return nbytes;
265 }
266 EXPORT_SYMBOL_GPL(comedi_buf_write_free);
267
268 unsigned int comedi_buf_read_n_available(struct comedi_async *async)
269 {
270         unsigned num_bytes;
271
272         if (!async)
273                 return 0;
274
275         num_bytes = async->munge_count - async->buf_read_count;
276
277         /*
278          * ensure the async buffer 'counts' are read before we
279          * attempt to read data from the buffer
280          */
281         smp_rmb();
282
283         return num_bytes;
284 }
285 EXPORT_SYMBOL_GPL(comedi_buf_read_n_available);
286
287 /* allocates a chunk for the reader from filled (and munged) buffer space */
288 unsigned int comedi_buf_read_alloc(struct comedi_async *async,
289                                    unsigned int nbytes)
290 {
291         unsigned int available;
292
293         available = async->munge_count - async->buf_read_alloc_count;
294         if (nbytes > available)
295                 nbytes = available;
296
297         async->buf_read_alloc_count += nbytes;
298
299         /*
300          * ensure the async buffer 'counts' are read before we
301          * attempt to read data from the read-alloc'ed buffer space
302          */
303         smp_rmb();
304
305         return nbytes;
306 }
307 EXPORT_SYMBOL_GPL(comedi_buf_read_alloc);
308
309 static unsigned int comedi_buf_read_n_allocated(struct comedi_async *async)
310 {
311         return async->buf_read_alloc_count - async->buf_read_count;
312 }
313
314 /* transfers control of a chunk from reader to free buffer space */
315 unsigned int comedi_buf_read_free(struct comedi_async *async,
316                                   unsigned int nbytes)
317 {
318         unsigned int allocated;
319
320         /*
321          * ensure data has been read out of buffer before
322          * the async read count is incremented
323          */
324         smp_mb();
325
326         allocated = comedi_buf_read_n_allocated(async);
327         if (nbytes > allocated)
328                 nbytes = allocated;
329
330         async->buf_read_count += nbytes;
331         async->buf_read_ptr += nbytes;
332         async->buf_read_ptr %= async->prealloc_bufsz;
333         return nbytes;
334 }
335 EXPORT_SYMBOL_GPL(comedi_buf_read_free);
336
337 int comedi_buf_put(struct comedi_async *async, short x)
338 {
339         unsigned int n = __comedi_buf_write_alloc(async, sizeof(short), 1);
340
341         if (n < sizeof(short)) {
342                 async->events |= COMEDI_CB_ERROR;
343                 return 0;
344         }
345         *(short *)(async->prealloc_buf + async->buf_write_ptr) = x;
346         comedi_buf_write_free(async, sizeof(short));
347         return 1;
348 }
349 EXPORT_SYMBOL_GPL(comedi_buf_put);
350
351 int comedi_buf_get(struct comedi_async *async, short *x)
352 {
353         unsigned int n = comedi_buf_read_n_available(async);
354
355         if (n < sizeof(short))
356                 return 0;
357         comedi_buf_read_alloc(async, sizeof(short));
358         *x = *(short *)(async->prealloc_buf + async->buf_read_ptr);
359         comedi_buf_read_free(async, sizeof(short));
360         return 1;
361 }
362 EXPORT_SYMBOL_GPL(comedi_buf_get);
363
364 void comedi_buf_memcpy_to(struct comedi_async *async, unsigned int offset,
365                           const void *data, unsigned int num_bytes)
366 {
367         unsigned int write_ptr = async->buf_write_ptr + offset;
368
369         if (write_ptr >= async->prealloc_bufsz)
370                 write_ptr %= async->prealloc_bufsz;
371
372         while (num_bytes) {
373                 unsigned int block_size;
374
375                 if (write_ptr + num_bytes > async->prealloc_bufsz)
376                         block_size = async->prealloc_bufsz - write_ptr;
377                 else
378                         block_size = num_bytes;
379
380                 memcpy(async->prealloc_buf + write_ptr, data, block_size);
381
382                 data += block_size;
383                 num_bytes -= block_size;
384
385                 write_ptr = 0;
386         }
387 }
388 EXPORT_SYMBOL_GPL(comedi_buf_memcpy_to);
389
390 void comedi_buf_memcpy_from(struct comedi_async *async, unsigned int offset,
391                             void *dest, unsigned int nbytes)
392 {
393         void *src;
394         unsigned int read_ptr = async->buf_read_ptr + offset;
395
396         if (read_ptr >= async->prealloc_bufsz)
397                 read_ptr %= async->prealloc_bufsz;
398
399         while (nbytes) {
400                 unsigned int block_size;
401
402                 src = async->prealloc_buf + read_ptr;
403
404                 if (nbytes >= async->prealloc_bufsz - read_ptr)
405                         block_size = async->prealloc_bufsz - read_ptr;
406                 else
407                         block_size = nbytes;
408
409                 memcpy(dest, src, block_size);
410                 nbytes -= block_size;
411                 dest += block_size;
412                 read_ptr = 0;
413         }
414 }
415 EXPORT_SYMBOL_GPL(comedi_buf_memcpy_from);