1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * ALSA sequencer Memory Manager |
4 | * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> |
5 | * Jaroslav Kysela <perex@perex.cz> |
6 | * 2000 by Takashi Iwai <tiwai@suse.de> |
7 | */ |
8 | |
9 | #include <linux/init.h> |
10 | #include <linux/export.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/sched/signal.h> |
13 | #include <linux/mm.h> |
14 | #include <sound/core.h> |
15 | |
16 | #include <sound/seq_kernel.h> |
17 | #include "seq_memory.h" |
18 | #include "seq_queue.h" |
19 | #include "seq_info.h" |
20 | #include "seq_lock.h" |
21 | |
22 | static inline int snd_seq_pool_available(struct snd_seq_pool *pool) |
23 | { |
24 | return pool->total_elements - atomic_read(v: &pool->counter); |
25 | } |
26 | |
27 | static inline int snd_seq_output_ok(struct snd_seq_pool *pool) |
28 | { |
29 | return snd_seq_pool_available(pool) >= pool->room; |
30 | } |
31 | |
32 | /* |
33 | * Variable length event: |
34 | * The event like sysex uses variable length type. |
35 | * The external data may be stored in three different formats. |
36 | * 1) kernel space |
37 | * This is the normal case. |
38 | * ext.data.len = length |
39 | * ext.data.ptr = buffer pointer |
40 | * 2) user space |
41 | * When an event is generated via read(), the external data is |
42 | * kept in user space until expanded. |
43 | * ext.data.len = length | SNDRV_SEQ_EXT_USRPTR |
44 | * ext.data.ptr = userspace pointer |
45 | * 3) chained cells |
46 | * When the variable length event is enqueued (in prioq or fifo), |
47 | * the external data is decomposed to several cells. |
48 | * ext.data.len = length | SNDRV_SEQ_EXT_CHAINED |
49 | * ext.data.ptr = the additiona cell head |
50 | * -> cell.next -> cell.next -> .. |
51 | */ |
52 | |
53 | /* |
54 | * exported: |
55 | * call dump function to expand external data. |
56 | */ |
57 | |
58 | static int get_var_len(const struct snd_seq_event *event) |
59 | { |
60 | if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) |
61 | return -EINVAL; |
62 | |
63 | return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; |
64 | } |
65 | |
66 | static int dump_var_event(const struct snd_seq_event *event, |
67 | snd_seq_dump_func_t func, void *private_data, |
68 | int offset, int maxlen) |
69 | { |
70 | int len, err; |
71 | struct snd_seq_event_cell *cell; |
72 | |
73 | len = get_var_len(event); |
74 | if (len <= 0) |
75 | return len; |
76 | if (len <= offset) |
77 | return 0; |
78 | if (maxlen && len > offset + maxlen) |
79 | len = offset + maxlen; |
80 | |
81 | if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { |
82 | char buf[32]; |
83 | char __user *curptr = (char __force __user *)event->data.ext.ptr; |
84 | curptr += offset; |
85 | len -= offset; |
86 | while (len > 0) { |
87 | int size = sizeof(buf); |
88 | if (len < size) |
89 | size = len; |
90 | if (copy_from_user(to: buf, from: curptr, n: size)) |
91 | return -EFAULT; |
92 | err = func(private_data, buf, size); |
93 | if (err < 0) |
94 | return err; |
95 | curptr += size; |
96 | len -= size; |
97 | } |
98 | return 0; |
99 | } |
100 | if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) |
101 | return func(private_data, event->data.ext.ptr + offset, |
102 | len - offset); |
103 | |
104 | cell = (struct snd_seq_event_cell *)event->data.ext.ptr; |
105 | for (; len > 0 && cell; cell = cell->next) { |
106 | int size = sizeof(struct snd_seq_event); |
107 | char *curptr = (char *)&cell->event; |
108 | |
109 | if (offset >= size) { |
110 | offset -= size; |
111 | len -= size; |
112 | continue; |
113 | } |
114 | if (len < size) |
115 | size = len; |
116 | err = func(private_data, curptr + offset, size - offset); |
117 | if (err < 0) |
118 | return err; |
119 | offset = 0; |
120 | len -= size; |
121 | } |
122 | return 0; |
123 | } |
124 | |
125 | int snd_seq_dump_var_event(const struct snd_seq_event *event, |
126 | snd_seq_dump_func_t func, void *private_data) |
127 | { |
128 | return dump_var_event(event, func, private_data, offset: 0, maxlen: 0); |
129 | } |
130 | EXPORT_SYMBOL(snd_seq_dump_var_event); |
131 | |
132 | |
133 | /* |
134 | * exported: |
135 | * expand the variable length event to linear buffer space. |
136 | */ |
137 | |
138 | static int seq_copy_in_kernel(void *ptr, void *src, int size) |
139 | { |
140 | char **bufptr = ptr; |
141 | |
142 | memcpy(*bufptr, src, size); |
143 | *bufptr += size; |
144 | return 0; |
145 | } |
146 | |
147 | static int seq_copy_in_user(void *ptr, void *src, int size) |
148 | { |
149 | char __user **bufptr = ptr; |
150 | |
151 | if (copy_to_user(to: *bufptr, from: src, n: size)) |
152 | return -EFAULT; |
153 | *bufptr += size; |
154 | return 0; |
155 | } |
156 | |
157 | static int expand_var_event(const struct snd_seq_event *event, |
158 | int offset, int size, char *buf, bool in_kernel) |
159 | { |
160 | if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { |
161 | if (! in_kernel) |
162 | return -EINVAL; |
163 | if (copy_from_user(to: buf, |
164 | from: (char __force __user *)event->data.ext.ptr + offset, |
165 | n: size)) |
166 | return -EFAULT; |
167 | return 0; |
168 | } |
169 | return dump_var_event(event, |
170 | func: in_kernel ? seq_copy_in_kernel : seq_copy_in_user, |
171 | private_data: &buf, offset, maxlen: size); |
172 | } |
173 | |
174 | int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, |
175 | int in_kernel, int size_aligned) |
176 | { |
177 | int len, newlen, err; |
178 | |
179 | len = get_var_len(event); |
180 | if (len < 0) |
181 | return len; |
182 | newlen = len; |
183 | if (size_aligned > 0) |
184 | newlen = roundup(len, size_aligned); |
185 | if (count < newlen) |
186 | return -EAGAIN; |
187 | err = expand_var_event(event, offset: 0, size: len, buf, in_kernel); |
188 | if (err < 0) |
189 | return err; |
190 | if (len != newlen) { |
191 | if (in_kernel) |
192 | memset(buf + len, 0, newlen - len); |
193 | else if (clear_user(to: (__force void __user *)buf + len, |
194 | n: newlen - len)) |
195 | return -EFAULT; |
196 | } |
197 | return newlen; |
198 | } |
199 | EXPORT_SYMBOL(snd_seq_expand_var_event); |
200 | |
201 | int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, |
202 | char *buf, int offset) |
203 | { |
204 | int len, err; |
205 | |
206 | len = get_var_len(event); |
207 | if (len < 0) |
208 | return len; |
209 | if (len <= offset) |
210 | return 0; |
211 | len -= offset; |
212 | if (len > count) |
213 | len = count; |
214 | err = expand_var_event(event, offset, size: count, buf, in_kernel: true); |
215 | if (err < 0) |
216 | return err; |
217 | return len; |
218 | } |
219 | EXPORT_SYMBOL_GPL(snd_seq_expand_var_event_at); |
220 | |
221 | /* |
222 | * release this cell, free extended data if available |
223 | */ |
224 | |
225 | static inline void free_cell(struct snd_seq_pool *pool, |
226 | struct snd_seq_event_cell *cell) |
227 | { |
228 | cell->next = pool->free; |
229 | pool->free = cell; |
230 | atomic_dec(v: &pool->counter); |
231 | } |
232 | |
233 | void snd_seq_cell_free(struct snd_seq_event_cell * cell) |
234 | { |
235 | struct snd_seq_pool *pool; |
236 | |
237 | if (snd_BUG_ON(!cell)) |
238 | return; |
239 | pool = cell->pool; |
240 | if (snd_BUG_ON(!pool)) |
241 | return; |
242 | |
243 | guard(spinlock_irqsave)(l: &pool->lock); |
244 | free_cell(pool, cell); |
245 | if (snd_seq_ev_is_variable(&cell->event)) { |
246 | if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) { |
247 | struct snd_seq_event_cell *curp, *nextptr; |
248 | curp = cell->event.data.ext.ptr; |
249 | for (; curp; curp = nextptr) { |
250 | nextptr = curp->next; |
251 | curp->next = pool->free; |
252 | free_cell(pool, cell: curp); |
253 | } |
254 | } |
255 | } |
256 | if (waitqueue_active(wq_head: &pool->output_sleep)) { |
257 | /* has enough space now? */ |
258 | if (snd_seq_output_ok(pool)) |
259 | wake_up(&pool->output_sleep); |
260 | } |
261 | } |
262 | |
263 | |
264 | /* |
265 | * allocate an event cell. |
266 | */ |
267 | static int snd_seq_cell_alloc(struct snd_seq_pool *pool, |
268 | struct snd_seq_event_cell **cellp, |
269 | int nonblock, struct file *file, |
270 | struct mutex *mutexp) |
271 | { |
272 | struct snd_seq_event_cell *cell; |
273 | unsigned long flags; |
274 | int err = -EAGAIN; |
275 | wait_queue_entry_t wait; |
276 | |
277 | if (pool == NULL) |
278 | return -EINVAL; |
279 | |
280 | *cellp = NULL; |
281 | |
282 | init_waitqueue_entry(wq_entry: &wait, current); |
283 | spin_lock_irqsave(&pool->lock, flags); |
284 | if (pool->ptr == NULL) { /* not initialized */ |
285 | pr_debug("ALSA: seq: pool is not initialized\n" ); |
286 | err = -EINVAL; |
287 | goto __error; |
288 | } |
289 | while (pool->free == NULL && ! nonblock && ! pool->closing) { |
290 | |
291 | set_current_state(TASK_INTERRUPTIBLE); |
292 | add_wait_queue(wq_head: &pool->output_sleep, wq_entry: &wait); |
293 | spin_unlock_irqrestore(lock: &pool->lock, flags); |
294 | if (mutexp) |
295 | mutex_unlock(lock: mutexp); |
296 | schedule(); |
297 | if (mutexp) |
298 | mutex_lock(mutexp); |
299 | spin_lock_irqsave(&pool->lock, flags); |
300 | remove_wait_queue(wq_head: &pool->output_sleep, wq_entry: &wait); |
301 | /* interrupted? */ |
302 | if (signal_pending(current)) { |
303 | err = -ERESTARTSYS; |
304 | goto __error; |
305 | } |
306 | } |
307 | if (pool->closing) { /* closing.. */ |
308 | err = -ENOMEM; |
309 | goto __error; |
310 | } |
311 | |
312 | cell = pool->free; |
313 | if (cell) { |
314 | int used; |
315 | pool->free = cell->next; |
316 | atomic_inc(v: &pool->counter); |
317 | used = atomic_read(v: &pool->counter); |
318 | if (pool->max_used < used) |
319 | pool->max_used = used; |
320 | pool->event_alloc_success++; |
321 | /* clear cell pointers */ |
322 | cell->next = NULL; |
323 | err = 0; |
324 | } else |
325 | pool->event_alloc_failures++; |
326 | *cellp = cell; |
327 | |
328 | __error: |
329 | spin_unlock_irqrestore(lock: &pool->lock, flags); |
330 | return err; |
331 | } |
332 | |
333 | |
334 | /* |
335 | * duplicate the event to a cell. |
336 | * if the event has external data, the data is decomposed to additional |
337 | * cells. |
338 | */ |
339 | int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, |
340 | struct snd_seq_event_cell **cellp, int nonblock, |
341 | struct file *file, struct mutex *mutexp) |
342 | { |
343 | int ncells, err; |
344 | unsigned int extlen; |
345 | struct snd_seq_event_cell *cell; |
346 | int size; |
347 | |
348 | *cellp = NULL; |
349 | |
350 | ncells = 0; |
351 | extlen = 0; |
352 | if (snd_seq_ev_is_variable(event)) { |
353 | extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; |
354 | ncells = DIV_ROUND_UP(extlen, sizeof(struct snd_seq_event)); |
355 | } |
356 | if (ncells >= pool->total_elements) |
357 | return -ENOMEM; |
358 | |
359 | err = snd_seq_cell_alloc(pool, cellp: &cell, nonblock, file, mutexp); |
360 | if (err < 0) |
361 | return err; |
362 | |
363 | /* copy the event */ |
364 | size = snd_seq_event_packet_size(ev: event); |
365 | memcpy(&cell->ump, event, size); |
366 | #if IS_ENABLED(CONFIG_SND_SEQ_UMP) |
367 | if (size < sizeof(cell->event)) |
368 | cell->ump.raw.extra = 0; |
369 | #endif |
370 | |
371 | /* decompose */ |
372 | if (snd_seq_ev_is_variable(event)) { |
373 | int len = extlen; |
374 | int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED; |
375 | int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR; |
376 | struct snd_seq_event_cell *src, *tmp, *tail; |
377 | char *buf; |
378 | |
379 | cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED; |
380 | cell->event.data.ext.ptr = NULL; |
381 | |
382 | src = (struct snd_seq_event_cell *)event->data.ext.ptr; |
383 | buf = (char *)event->data.ext.ptr; |
384 | tail = NULL; |
385 | |
386 | while (ncells-- > 0) { |
387 | size = sizeof(struct snd_seq_event); |
388 | if (len < size) |
389 | size = len; |
390 | err = snd_seq_cell_alloc(pool, cellp: &tmp, nonblock, file, |
391 | mutexp); |
392 | if (err < 0) |
393 | goto __error; |
394 | if (cell->event.data.ext.ptr == NULL) |
395 | cell->event.data.ext.ptr = tmp; |
396 | if (tail) |
397 | tail->next = tmp; |
398 | tail = tmp; |
399 | /* copy chunk */ |
400 | if (is_chained && src) { |
401 | tmp->event = src->event; |
402 | src = src->next; |
403 | } else if (is_usrptr) { |
404 | if (copy_from_user(to: &tmp->event, from: (char __force __user *)buf, n: size)) { |
405 | err = -EFAULT; |
406 | goto __error; |
407 | } |
408 | } else { |
409 | memcpy(&tmp->event, buf, size); |
410 | } |
411 | buf += size; |
412 | len -= size; |
413 | } |
414 | } |
415 | |
416 | *cellp = cell; |
417 | return 0; |
418 | |
419 | __error: |
420 | snd_seq_cell_free(cell); |
421 | return err; |
422 | } |
423 | |
424 | |
425 | /* poll wait */ |
426 | int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, |
427 | poll_table *wait) |
428 | { |
429 | poll_wait(filp: file, wait_address: &pool->output_sleep, p: wait); |
430 | return snd_seq_output_ok(pool); |
431 | } |
432 | |
433 | |
434 | /* allocate room specified number of events */ |
435 | int snd_seq_pool_init(struct snd_seq_pool *pool) |
436 | { |
437 | int cell; |
438 | struct snd_seq_event_cell *cellptr; |
439 | |
440 | if (snd_BUG_ON(!pool)) |
441 | return -EINVAL; |
442 | |
443 | cellptr = kvmalloc_array(n: pool->size, |
444 | size: sizeof(struct snd_seq_event_cell), |
445 | GFP_KERNEL); |
446 | if (!cellptr) |
447 | return -ENOMEM; |
448 | |
449 | /* add new cells to the free cell list */ |
450 | guard(spinlock_irq)(l: &pool->lock); |
451 | if (pool->ptr) { |
452 | kvfree(addr: cellptr); |
453 | return 0; |
454 | } |
455 | |
456 | pool->ptr = cellptr; |
457 | pool->free = NULL; |
458 | |
459 | for (cell = 0; cell < pool->size; cell++) { |
460 | cellptr = pool->ptr + cell; |
461 | cellptr->pool = pool; |
462 | cellptr->next = pool->free; |
463 | pool->free = cellptr; |
464 | } |
465 | pool->room = (pool->size + 1) / 2; |
466 | |
467 | /* init statistics */ |
468 | pool->max_used = 0; |
469 | pool->total_elements = pool->size; |
470 | return 0; |
471 | } |
472 | |
473 | /* refuse the further insertion to the pool */ |
474 | void snd_seq_pool_mark_closing(struct snd_seq_pool *pool) |
475 | { |
476 | if (snd_BUG_ON(!pool)) |
477 | return; |
478 | guard(spinlock_irqsave)(l: &pool->lock); |
479 | pool->closing = 1; |
480 | } |
481 | |
482 | /* remove events */ |
483 | int snd_seq_pool_done(struct snd_seq_pool *pool) |
484 | { |
485 | struct snd_seq_event_cell *ptr; |
486 | |
487 | if (snd_BUG_ON(!pool)) |
488 | return -EINVAL; |
489 | |
490 | /* wait for closing all threads */ |
491 | if (waitqueue_active(wq_head: &pool->output_sleep)) |
492 | wake_up(&pool->output_sleep); |
493 | |
494 | while (atomic_read(v: &pool->counter) > 0) |
495 | schedule_timeout_uninterruptible(timeout: 1); |
496 | |
497 | /* release all resources */ |
498 | scoped_guard(spinlock_irq, &pool->lock) { |
499 | ptr = pool->ptr; |
500 | pool->ptr = NULL; |
501 | pool->free = NULL; |
502 | pool->total_elements = 0; |
503 | } |
504 | |
505 | kvfree(addr: ptr); |
506 | |
507 | guard(spinlock_irq)(l: &pool->lock); |
508 | pool->closing = 0; |
509 | |
510 | return 0; |
511 | } |
512 | |
513 | |
514 | /* init new memory pool */ |
515 | struct snd_seq_pool *snd_seq_pool_new(int poolsize) |
516 | { |
517 | struct snd_seq_pool *pool; |
518 | |
519 | /* create pool block */ |
520 | pool = kzalloc(size: sizeof(*pool), GFP_KERNEL); |
521 | if (!pool) |
522 | return NULL; |
523 | spin_lock_init(&pool->lock); |
524 | pool->ptr = NULL; |
525 | pool->free = NULL; |
526 | pool->total_elements = 0; |
527 | atomic_set(v: &pool->counter, i: 0); |
528 | pool->closing = 0; |
529 | init_waitqueue_head(&pool->output_sleep); |
530 | |
531 | pool->size = poolsize; |
532 | |
533 | /* init statistics */ |
534 | pool->max_used = 0; |
535 | return pool; |
536 | } |
537 | |
538 | /* remove memory pool */ |
539 | int snd_seq_pool_delete(struct snd_seq_pool **ppool) |
540 | { |
541 | struct snd_seq_pool *pool = *ppool; |
542 | |
543 | *ppool = NULL; |
544 | if (pool == NULL) |
545 | return 0; |
546 | snd_seq_pool_mark_closing(pool); |
547 | snd_seq_pool_done(pool); |
548 | kfree(objp: pool); |
549 | return 0; |
550 | } |
551 | |
552 | /* exported to seq_clientmgr.c */ |
553 | void snd_seq_info_pool(struct snd_info_buffer *buffer, |
554 | struct snd_seq_pool *pool, char *space) |
555 | { |
556 | if (pool == NULL) |
557 | return; |
558 | snd_iprintf(buffer, "%sPool size : %d\n" , space, pool->total_elements); |
559 | snd_iprintf(buffer, "%sCells in use : %d\n" , space, atomic_read(&pool->counter)); |
560 | snd_iprintf(buffer, "%sPeak cells in use : %d\n" , space, pool->max_used); |
561 | snd_iprintf(buffer, "%sAlloc success : %d\n" , space, pool->event_alloc_success); |
562 | snd_iprintf(buffer, "%sAlloc failures : %d\n" , space, pool->event_alloc_failures); |
563 | } |
564 | |