diff options
Diffstat (limited to 'sound/core/seq/seq_fifo.c')
-rw-r--r-- | sound/core/seq/seq_fifo.c | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c new file mode 100644 index 000000000..6a2473270 --- /dev/null +++ b/sound/core/seq/seq_fifo.c @@ -0,0 +1,299 @@ +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/core.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include "seq_fifo.h" +#include "seq_lock.h" + + +/* FIFO */ + +/* create new fifo */ +struct snd_seq_fifo *snd_seq_fifo_new(int poolsize) +{ + struct snd_seq_fifo *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + f->pool = snd_seq_pool_new(poolsize); + if (f->pool == NULL) { + kfree(f); + return NULL; + } + if (snd_seq_pool_init(f->pool) < 0) { + snd_seq_pool_delete(&f->pool); + kfree(f); + return NULL; + } + + spin_lock_init(&f->lock); + snd_use_lock_init(&f->use_lock); + init_waitqueue_head(&f->input_sleep); + atomic_set(&f->overflow, 0); + + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +void snd_seq_fifo_delete(struct snd_seq_fifo **fifo) +{ + struct snd_seq_fifo *f; + + if (snd_BUG_ON(!fifo)) + return; + f = *fifo; + if (snd_BUG_ON(!f)) + return; + *fifo = NULL; + + if (f->pool) + snd_seq_pool_mark_closing(f->pool); + + snd_seq_fifo_clear(f); + + /* wake up clients if any */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + /* release resources...*/ + /*....................*/ + + if (f->pool) { + snd_seq_pool_done(f->pool); + snd_seq_pool_delete(&f->pool); + } + + kfree(f); +} + +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f); + +/* clear queue */ +void snd_seq_fifo_clear(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + + /* clear overflow flag */ + atomic_set(&f->overflow, 0); + + snd_use_lock_sync(&f->use_lock); + spin_lock_irqsave(&f->lock, flags); + /* drain the fifo */ + while ((cell = fifo_cell_out(f)) != NULL) { + snd_seq_cell_free(cell); + } + spin_unlock_irqrestore(&f->lock, flags); +} + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(struct snd_seq_fifo *f, + struct snd_seq_event *event) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + int err; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + snd_use_lock_use(&f->use_lock); + err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL, NULL); /* always non-blocking */ + if (err < 0) { + if ((err == -ENOMEM) || (err == -EAGAIN)) + atomic_inc(&f->overflow); + snd_use_lock_free(&f->use_lock); + return err; + } + + /* append new cells to fifo */ + spin_lock_irqsave(&f->lock, flags); + if (f->tail != NULL) + f->tail->next = cell; + f->tail = cell; + if (f->head == NULL) + f->head = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + + /* wakeup client */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + snd_use_lock_free(&f->use_lock); + + return 0; /* success */ + +} + +/* dequeue cell from fifo */ +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + + if ((cell = f->head) != NULL) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + return cell; +} + +/* dequeue cell from fifo and copy on user space */ +int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, + struct snd_seq_event_cell **cellp, int nonblock) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + wait_queue_entry_t wait; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + *cellp = NULL; + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&f->lock, flags); + while ((cell = fifo_cell_out(f)) == NULL) { + if (nonblock) { + /* non-blocking - return immediately */ + spin_unlock_irqrestore(&f->lock, flags); + return -EAGAIN; + } + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&f->input_sleep, &wait); + spin_unlock_irq(&f->lock); + schedule(); + spin_lock_irq(&f->lock); + remove_wait_queue(&f->input_sleep, &wait); + if (signal_pending(current)) { + spin_unlock_irqrestore(&f->lock, flags); + return -ERESTARTSYS; + } + } + spin_unlock_irqrestore(&f->lock, flags); + *cellp = cell; + + return 0; +} + + +void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, + struct snd_seq_event_cell *cell) +{ + unsigned long flags; + + if (cell) { + spin_lock_irqsave(&f->lock, flags); + cell->next = f->head; + f->head = cell; + if (!f->tail) + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + } +} + + +/* polling; return non-zero if queue is available */ +int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, + poll_table *wait) +{ + poll_wait(file, &f->input_sleep, wait); + return (f->cells > 0); +} + +/* change the size of pool; all old events are removed */ +int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize) +{ + unsigned long flags; + struct snd_seq_pool *newpool, *oldpool; + struct snd_seq_event_cell *cell, *next, *oldhead; + + if (snd_BUG_ON(!f || !f->pool)) + return -EINVAL; + + /* allocate new pool */ + newpool = snd_seq_pool_new(poolsize); + if (newpool == NULL) + return -ENOMEM; + if (snd_seq_pool_init(newpool) < 0) { + snd_seq_pool_delete(&newpool); + return -ENOMEM; + } + + spin_lock_irqsave(&f->lock, flags); + /* remember old pool */ + oldpool = f->pool; + oldhead = f->head; + /* exchange pools */ + f->pool = newpool; + f->head = NULL; + f->tail = NULL; + f->cells = 0; + /* NOTE: overflow flag is not cleared */ + spin_unlock_irqrestore(&f->lock, flags); + + /* close the old pool and wait until all users are gone */ + snd_seq_pool_mark_closing(oldpool); + snd_use_lock_sync(&f->use_lock); + + /* release cells in old pool */ + for (cell = oldhead; cell; cell = next) { + next = cell->next; + snd_seq_cell_free(cell); + } + snd_seq_pool_delete(&oldpool); + + return 0; +} + +/* get the number of unused cells safely */ +int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f) +{ + unsigned long flags; + int cells; + + if (!f) + return 0; + + snd_use_lock_use(&f->use_lock); + spin_lock_irqsave(&f->lock, flags); + cells = snd_seq_unused_cells(f->pool); + spin_unlock_irqrestore(&f->lock, flags); + snd_use_lock_free(&f->use_lock); + return cells; +} |