// SPDX-License-Identifier: GPL-2.0-or-later/* * ALSA sequencer FIFO * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> */ #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_obj(*f); 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; /* clear overflow flag */ atomic_set(&f->overflow, 0); snd_use_lock_sync(&f->use_lock); guard(spinlock_irq)(&f->lock); /* drain the fifo */ while ((cell = fifo_cell_out(f)) != NULL) { snd_seq_cell_free(cell); }} /* 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; struct snd_seq_pool *pool; bool linked; int err; if (snd_BUG_ON(!f)) return -EINVAL; guard(snd_seq_fifo)(f);retry: pool = READ_ONCE(f->pool); err = snd_seq_event_dup(pool, event, &cell, 1, NULL, NULL); /* always non-blocking */ if (err < 0) { if ((err == -ENOMEM) || (err == -EAGAIN)) atomic_inc(&f->overflow); return err; } /* append new cells to fifo */ linked = false; scoped_guard(spinlock_irqsave, &f->lock) { if (cell->pool == f->pool) { if (f->tail) f->tail->next = cell; f->tail = cell; if (!f->head) f->head = cell; cell->next = NULL; f->cells++; linked = true; } } if (!linked) { /* Retry against the replacement pool after resize publishes it. */ snd_seq_cell_free(cell); goto retry; } /* wakeup client */ if (waitqueue_active(&f->input_sleep)) wake_up(&f->input_sleep); 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; cell = f->head; if (cell) { 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_irqrestore(&f->lock, flags); schedule(); spin_lock_irqsave(&f->lock, flags); 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){ bool linked = false; if (cell) { scoped_guard(spinlock_irqsave, &f->lock) { if (cell->pool == f->pool) { cell->next = f->head; f->head = cell; if (!f->tail) f->tail = cell; f->cells++; linked = true; } } if (!linked) snd_seq_cell_free(cell); }} /* 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); guard(spinlock_irq)(&f->lock); 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){ 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; } scoped_guard(spinlock_irq, &f->lock) { /* remember old pool */ oldpool = f->pool; oldhead = f->head; /* exchange pools */ WRITE_ONCE(f->pool, newpool); f->head = NULL; f->tail = NULL; f->cells = 0; /* NOTE: overflow flag is not cleared */ } /* 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){ if (!f) return 0; guard(snd_seq_fifo)(f); guard(spinlock_irqsave)(&f->lock); return snd_seq_unused_cells(f->pool);}
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ALSA sequencer FIFO
* Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl>
*/
#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_obj(*f);
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;
/* clear overflow flag */
atomic_set(&f->overflow, 0);
snd_use_lock_sync(&f->use_lock);
guard(spinlock_irq)(&f->lock);
/* drain the fifo */
while ((cell = fifo_cell_out(f)) != NULL) {
snd_seq_cell_free(cell);
}
}
/* 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;
struct snd_seq_pool *pool;
bool linked;
int err;
if (snd_BUG_ON(!f))
return -EINVAL;
guard(snd_seq_fifo)(f);
retry:
pool = READ_ONCE(f->pool);
err = snd_seq_event_dup(pool, event, &cell, 1, NULL, NULL); /* always non-blocking */
if (err < 0) {
if ((err == -ENOMEM) || (err == -EAGAIN))
atomic_inc(&f->overflow);
return err;
}
/* append new cells to fifo */
linked = false;
scoped_guard(spinlock_irqsave, &f->lock) {
if (cell->pool == f->pool) {
if (f->tail)
f->tail->next = cell;
f->tail = cell;
if (!f->head)
f->head = cell;
cell->next = NULL;
f->cells++;
linked = true;
}
}
if (!linked) {
/* Retry against the replacement pool after resize publishes it. */
snd_seq_cell_free(cell);
goto retry;
}
/* wakeup client */
if (waitqueue_active(&f->input_sleep))
wake_up(&f->input_sleep);
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;
cell = f->head;
if (cell) {
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_irqrestore(&f->lock, flags);
schedule();
spin_lock_irqsave(&f->lock, flags);
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)
{
bool linked = false;
if (cell) {
scoped_guard(spinlock_irqsave, &f->lock) {
if (cell->pool == f->pool) {
cell->next = f->head;
f->head = cell;
if (!f->tail)
f->tail = cell;
f->cells++;
linked = true;
}
}
if (!linked)
snd_seq_cell_free(cell);
}
}
/* 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);
guard(spinlock_irq)(&f->lock);
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)
{
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;
}
scoped_guard(spinlock_irq, &f->lock) {
/* remember old pool */
oldpool = f->pool;
oldhead = f->head;
/* exchange pools */
WRITE_ONCE(f->pool, newpool);
f->head = NULL;
f->tail = NULL;
f->cells = 0;
/* NOTE: overflow flag is not cleared */
}
/* 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)
{
if (!f)
return 0;
guard(snd_seq_fifo)(f);
guard(spinlock_irqsave)(&f->lock);
return snd_seq_unused_cells(f->pool);
}