5a2972600a
commit dc7497795e014d84699c3b8809ed6df35352dd74 upstream. snd_seq_check_queue() passes the current tick and time of the given queue as a pointer to snd_seq_prioq_cell_out(), but those might be updated concurrently by the seq timer update. Fix it by retrieving the current tick and time via the proper helper functions at first, and pass those values to snd_seq_prioq_cell_out() later in the loops. snd_seq_timer_get_cur_time() takes a new argument and adjusts with the current system time only when it's requested so; this update isn't needed for snd_seq_check_queue(), as it's called either from the interrupt handler or right after queuing. Also, snd_seq_timer_get_cur_tick() is changed to read the value in the spinlock for the concurrency, too. Reported-by: syzbot+fd5e0eaa1a32999173b2@syzkaller.appspotmail.com Link: https://lore.kernel.org/r/20200214111316.26939-3-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
797 lines
20 KiB
C
797 lines
20 KiB
C
/*
|
|
* ALSA sequencer Timing queue handling
|
|
* Copyright (c) 1998-1999 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
|
|
*
|
|
* MAJOR CHANGES
|
|
* Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de>
|
|
* - Queues are allocated dynamically via ioctl.
|
|
* - When owner client is deleted, all owned queues are deleted, too.
|
|
* - Owner of unlocked queue is kept unmodified even if it is
|
|
* manipulated by other clients.
|
|
* - Owner field in SET_QUEUE_OWNER ioctl must be identical with the
|
|
* caller client. i.e. Changing owner to a third client is not
|
|
* allowed.
|
|
*
|
|
* Aug. 30, 2000 Takashi Iwai
|
|
* - Queues are managed in static array again, but with better way.
|
|
* The API itself is identical.
|
|
* - The queue is locked when struct snd_seq_queue pointer is returned via
|
|
* queueptr(). This pointer *MUST* be released afterward by
|
|
* queuefree(ptr).
|
|
* - Addition of experimental sync support.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <sound/core.h>
|
|
|
|
#include "seq_memory.h"
|
|
#include "seq_queue.h"
|
|
#include "seq_clientmgr.h"
|
|
#include "seq_fifo.h"
|
|
#include "seq_timer.h"
|
|
#include "seq_info.h"
|
|
|
|
/* list of allocated queues */
|
|
static struct snd_seq_queue *queue_list[SNDRV_SEQ_MAX_QUEUES];
|
|
static DEFINE_SPINLOCK(queue_list_lock);
|
|
/* number of queues allocated */
|
|
static int num_queues;
|
|
|
|
int snd_seq_queue_get_cur_queues(void)
|
|
{
|
|
return num_queues;
|
|
}
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/* assign queue id and insert to list */
|
|
static int queue_list_add(struct snd_seq_queue *q)
|
|
{
|
|
int i;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&queue_list_lock, flags);
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if (! queue_list[i]) {
|
|
queue_list[i] = q;
|
|
q->queue = i;
|
|
num_queues++;
|
|
spin_unlock_irqrestore(&queue_list_lock, flags);
|
|
return i;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&queue_list_lock, flags);
|
|
return -1;
|
|
}
|
|
|
|
static struct snd_seq_queue *queue_list_remove(int id, int client)
|
|
{
|
|
struct snd_seq_queue *q;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&queue_list_lock, flags);
|
|
q = queue_list[id];
|
|
if (q) {
|
|
spin_lock(&q->owner_lock);
|
|
if (q->owner == client) {
|
|
/* found */
|
|
q->klocked = 1;
|
|
spin_unlock(&q->owner_lock);
|
|
queue_list[id] = NULL;
|
|
num_queues--;
|
|
spin_unlock_irqrestore(&queue_list_lock, flags);
|
|
return q;
|
|
}
|
|
spin_unlock(&q->owner_lock);
|
|
}
|
|
spin_unlock_irqrestore(&queue_list_lock, flags);
|
|
return NULL;
|
|
}
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/* create new queue (constructor) */
|
|
static struct snd_seq_queue *queue_new(int owner, int locked)
|
|
{
|
|
struct snd_seq_queue *q;
|
|
|
|
q = kzalloc(sizeof(*q), GFP_KERNEL);
|
|
if (!q)
|
|
return NULL;
|
|
|
|
spin_lock_init(&q->owner_lock);
|
|
spin_lock_init(&q->check_lock);
|
|
mutex_init(&q->timer_mutex);
|
|
snd_use_lock_init(&q->use_lock);
|
|
q->queue = -1;
|
|
|
|
q->tickq = snd_seq_prioq_new();
|
|
q->timeq = snd_seq_prioq_new();
|
|
q->timer = snd_seq_timer_new();
|
|
if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) {
|
|
snd_seq_prioq_delete(&q->tickq);
|
|
snd_seq_prioq_delete(&q->timeq);
|
|
snd_seq_timer_delete(&q->timer);
|
|
kfree(q);
|
|
return NULL;
|
|
}
|
|
|
|
q->owner = owner;
|
|
q->locked = locked;
|
|
q->klocked = 0;
|
|
|
|
return q;
|
|
}
|
|
|
|
/* delete queue (destructor) */
|
|
static void queue_delete(struct snd_seq_queue *q)
|
|
{
|
|
/* stop and release the timer */
|
|
mutex_lock(&q->timer_mutex);
|
|
snd_seq_timer_stop(q->timer);
|
|
snd_seq_timer_close(q);
|
|
mutex_unlock(&q->timer_mutex);
|
|
/* wait until access free */
|
|
snd_use_lock_sync(&q->use_lock);
|
|
/* release resources... */
|
|
snd_seq_prioq_delete(&q->tickq);
|
|
snd_seq_prioq_delete(&q->timeq);
|
|
snd_seq_timer_delete(&q->timer);
|
|
|
|
kfree(q);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/* delete all existing queues */
|
|
void snd_seq_queues_delete(void)
|
|
{
|
|
int i;
|
|
|
|
/* clear list */
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if (queue_list[i])
|
|
queue_delete(queue_list[i]);
|
|
}
|
|
}
|
|
|
|
static void queue_use(struct snd_seq_queue *queue, int client, int use);
|
|
|
|
/* allocate a new queue -
|
|
* return pointer to new queue or ERR_PTR(-errno) for error
|
|
* The new queue's use_lock is set to 1. It is the caller's responsibility to
|
|
* call snd_use_lock_free(&q->use_lock).
|
|
*/
|
|
struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int info_flags)
|
|
{
|
|
struct snd_seq_queue *q;
|
|
|
|
q = queue_new(client, locked);
|
|
if (q == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
q->info_flags = info_flags;
|
|
queue_use(q, client, 1);
|
|
snd_use_lock_use(&q->use_lock);
|
|
if (queue_list_add(q) < 0) {
|
|
snd_use_lock_free(&q->use_lock);
|
|
queue_delete(q);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
return q;
|
|
}
|
|
|
|
/* delete a queue - queue must be owned by the client */
|
|
int snd_seq_queue_delete(int client, int queueid)
|
|
{
|
|
struct snd_seq_queue *q;
|
|
|
|
if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
|
|
return -EINVAL;
|
|
q = queue_list_remove(queueid, client);
|
|
if (q == NULL)
|
|
return -EINVAL;
|
|
queue_delete(q);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* return pointer to queue structure for specified id */
|
|
struct snd_seq_queue *queueptr(int queueid)
|
|
{
|
|
struct snd_seq_queue *q;
|
|
unsigned long flags;
|
|
|
|
if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)
|
|
return NULL;
|
|
spin_lock_irqsave(&queue_list_lock, flags);
|
|
q = queue_list[queueid];
|
|
if (q)
|
|
snd_use_lock_use(&q->use_lock);
|
|
spin_unlock_irqrestore(&queue_list_lock, flags);
|
|
return q;
|
|
}
|
|
|
|
/* return the (first) queue matching with the specified name */
|
|
struct snd_seq_queue *snd_seq_queue_find_name(char *name)
|
|
{
|
|
int i;
|
|
struct snd_seq_queue *q;
|
|
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if ((q = queueptr(i)) != NULL) {
|
|
if (strncmp(q->name, name, sizeof(q->name)) == 0)
|
|
return q;
|
|
queuefree(q);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------- */
|
|
|
|
void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop)
|
|
{
|
|
unsigned long flags;
|
|
struct snd_seq_event_cell *cell;
|
|
snd_seq_tick_time_t cur_tick;
|
|
snd_seq_real_time_t cur_time;
|
|
|
|
if (q == NULL)
|
|
return;
|
|
|
|
/* make this function non-reentrant */
|
|
spin_lock_irqsave(&q->check_lock, flags);
|
|
if (q->check_blocked) {
|
|
q->check_again = 1;
|
|
spin_unlock_irqrestore(&q->check_lock, flags);
|
|
return; /* other thread is already checking queues */
|
|
}
|
|
q->check_blocked = 1;
|
|
spin_unlock_irqrestore(&q->check_lock, flags);
|
|
|
|
__again:
|
|
/* Process tick queue... */
|
|
cur_tick = snd_seq_timer_get_cur_tick(q->timer);
|
|
for (;;) {
|
|
cell = snd_seq_prioq_cell_out(q->tickq, &cur_tick);
|
|
if (!cell)
|
|
break;
|
|
snd_seq_dispatch_event(cell, atomic, hop);
|
|
}
|
|
|
|
/* Process time queue... */
|
|
cur_time = snd_seq_timer_get_cur_time(q->timer, false);
|
|
for (;;) {
|
|
cell = snd_seq_prioq_cell_out(q->timeq, &cur_time);
|
|
if (!cell)
|
|
break;
|
|
snd_seq_dispatch_event(cell, atomic, hop);
|
|
}
|
|
|
|
/* free lock */
|
|
spin_lock_irqsave(&q->check_lock, flags);
|
|
if (q->check_again) {
|
|
q->check_again = 0;
|
|
spin_unlock_irqrestore(&q->check_lock, flags);
|
|
goto __again;
|
|
}
|
|
q->check_blocked = 0;
|
|
spin_unlock_irqrestore(&q->check_lock, flags);
|
|
}
|
|
|
|
|
|
/* enqueue a event to singe queue */
|
|
int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop)
|
|
{
|
|
int dest, err;
|
|
struct snd_seq_queue *q;
|
|
|
|
if (snd_BUG_ON(!cell))
|
|
return -EINVAL;
|
|
dest = cell->event.queue; /* destination queue */
|
|
q = queueptr(dest);
|
|
if (q == NULL)
|
|
return -EINVAL;
|
|
/* handle relative time stamps, convert them into absolute */
|
|
if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) {
|
|
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
|
|
case SNDRV_SEQ_TIME_STAMP_TICK:
|
|
cell->event.time.tick += q->timer->tick.cur_tick;
|
|
break;
|
|
|
|
case SNDRV_SEQ_TIME_STAMP_REAL:
|
|
snd_seq_inc_real_time(&cell->event.time.time,
|
|
&q->timer->cur_time);
|
|
break;
|
|
}
|
|
cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK;
|
|
cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS;
|
|
}
|
|
/* enqueue event in the real-time or midi queue */
|
|
switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {
|
|
case SNDRV_SEQ_TIME_STAMP_TICK:
|
|
err = snd_seq_prioq_cell_in(q->tickq, cell);
|
|
break;
|
|
|
|
case SNDRV_SEQ_TIME_STAMP_REAL:
|
|
default:
|
|
err = snd_seq_prioq_cell_in(q->timeq, cell);
|
|
break;
|
|
}
|
|
|
|
if (err < 0) {
|
|
queuefree(q); /* unlock */
|
|
return err;
|
|
}
|
|
|
|
/* trigger dispatching */
|
|
snd_seq_check_queue(q, atomic, hop);
|
|
|
|
queuefree(q); /* unlock */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
static inline int check_access(struct snd_seq_queue *q, int client)
|
|
{
|
|
return (q->owner == client) || (!q->locked && !q->klocked);
|
|
}
|
|
|
|
/* check if the client has permission to modify queue parameters.
|
|
* if it does, lock the queue
|
|
*/
|
|
static int queue_access_lock(struct snd_seq_queue *q, int client)
|
|
{
|
|
unsigned long flags;
|
|
int access_ok;
|
|
|
|
spin_lock_irqsave(&q->owner_lock, flags);
|
|
access_ok = check_access(q, client);
|
|
if (access_ok)
|
|
q->klocked = 1;
|
|
spin_unlock_irqrestore(&q->owner_lock, flags);
|
|
return access_ok;
|
|
}
|
|
|
|
/* unlock the queue */
|
|
static inline void queue_access_unlock(struct snd_seq_queue *q)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&q->owner_lock, flags);
|
|
q->klocked = 0;
|
|
spin_unlock_irqrestore(&q->owner_lock, flags);
|
|
}
|
|
|
|
/* exported - only checking permission */
|
|
int snd_seq_queue_check_access(int queueid, int client)
|
|
{
|
|
struct snd_seq_queue *q = queueptr(queueid);
|
|
int access_ok;
|
|
unsigned long flags;
|
|
|
|
if (! q)
|
|
return 0;
|
|
spin_lock_irqsave(&q->owner_lock, flags);
|
|
access_ok = check_access(q, client);
|
|
spin_unlock_irqrestore(&q->owner_lock, flags);
|
|
queuefree(q);
|
|
return access_ok;
|
|
}
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/*
|
|
* change queue's owner and permission
|
|
*/
|
|
int snd_seq_queue_set_owner(int queueid, int client, int locked)
|
|
{
|
|
struct snd_seq_queue *q = queueptr(queueid);
|
|
unsigned long flags;
|
|
|
|
if (q == NULL)
|
|
return -EINVAL;
|
|
|
|
if (! queue_access_lock(q, client)) {
|
|
queuefree(q);
|
|
return -EPERM;
|
|
}
|
|
|
|
spin_lock_irqsave(&q->owner_lock, flags);
|
|
q->locked = locked ? 1 : 0;
|
|
q->owner = client;
|
|
spin_unlock_irqrestore(&q->owner_lock, flags);
|
|
queue_access_unlock(q);
|
|
queuefree(q);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/* open timer -
|
|
* q->use mutex should be down before calling this function to avoid
|
|
* confliction with snd_seq_queue_use()
|
|
*/
|
|
int snd_seq_queue_timer_open(int queueid)
|
|
{
|
|
int result = 0;
|
|
struct snd_seq_queue *queue;
|
|
struct snd_seq_timer *tmr;
|
|
|
|
queue = queueptr(queueid);
|
|
if (queue == NULL)
|
|
return -EINVAL;
|
|
tmr = queue->timer;
|
|
if ((result = snd_seq_timer_open(queue)) < 0) {
|
|
snd_seq_timer_defaults(tmr);
|
|
result = snd_seq_timer_open(queue);
|
|
}
|
|
queuefree(queue);
|
|
return result;
|
|
}
|
|
|
|
/* close timer -
|
|
* q->use mutex should be down before calling this function
|
|
*/
|
|
int snd_seq_queue_timer_close(int queueid)
|
|
{
|
|
struct snd_seq_queue *queue;
|
|
int result = 0;
|
|
|
|
queue = queueptr(queueid);
|
|
if (queue == NULL)
|
|
return -EINVAL;
|
|
snd_seq_timer_close(queue);
|
|
queuefree(queue);
|
|
return result;
|
|
}
|
|
|
|
/* change queue tempo and ppq */
|
|
int snd_seq_queue_timer_set_tempo(int queueid, int client,
|
|
struct snd_seq_queue_tempo *info)
|
|
{
|
|
struct snd_seq_queue *q = queueptr(queueid);
|
|
int result;
|
|
|
|
if (q == NULL)
|
|
return -EINVAL;
|
|
if (! queue_access_lock(q, client)) {
|
|
queuefree(q);
|
|
return -EPERM;
|
|
}
|
|
|
|
result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq);
|
|
if (result >= 0 && info->skew_base > 0)
|
|
result = snd_seq_timer_set_skew(q->timer, info->skew_value,
|
|
info->skew_base);
|
|
queue_access_unlock(q);
|
|
queuefree(q);
|
|
return result;
|
|
}
|
|
|
|
/* use or unuse this queue */
|
|
static void queue_use(struct snd_seq_queue *queue, int client, int use)
|
|
{
|
|
if (use) {
|
|
if (!test_and_set_bit(client, queue->clients_bitmap))
|
|
queue->clients++;
|
|
} else {
|
|
if (test_and_clear_bit(client, queue->clients_bitmap))
|
|
queue->clients--;
|
|
}
|
|
if (queue->clients) {
|
|
if (use && queue->clients == 1)
|
|
snd_seq_timer_defaults(queue->timer);
|
|
snd_seq_timer_open(queue);
|
|
} else {
|
|
snd_seq_timer_close(queue);
|
|
}
|
|
}
|
|
|
|
/* use or unuse this queue -
|
|
* if it is the first client, starts the timer.
|
|
* if it is not longer used by any clients, stop the timer.
|
|
*/
|
|
int snd_seq_queue_use(int queueid, int client, int use)
|
|
{
|
|
struct snd_seq_queue *queue;
|
|
|
|
queue = queueptr(queueid);
|
|
if (queue == NULL)
|
|
return -EINVAL;
|
|
mutex_lock(&queue->timer_mutex);
|
|
queue_use(queue, client, use);
|
|
mutex_unlock(&queue->timer_mutex);
|
|
queuefree(queue);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* check if queue is used by the client
|
|
* return negative value if the queue is invalid.
|
|
* return 0 if not used, 1 if used.
|
|
*/
|
|
int snd_seq_queue_is_used(int queueid, int client)
|
|
{
|
|
struct snd_seq_queue *q;
|
|
int result;
|
|
|
|
q = queueptr(queueid);
|
|
if (q == NULL)
|
|
return -EINVAL; /* invalid queue */
|
|
result = test_bit(client, q->clients_bitmap) ? 1 : 0;
|
|
queuefree(q);
|
|
return result;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/* notification that client has left the system -
|
|
* stop the timer on all queues owned by this client
|
|
*/
|
|
void snd_seq_queue_client_termination(int client)
|
|
{
|
|
unsigned long flags;
|
|
int i;
|
|
struct snd_seq_queue *q;
|
|
bool matched;
|
|
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if ((q = queueptr(i)) == NULL)
|
|
continue;
|
|
spin_lock_irqsave(&q->owner_lock, flags);
|
|
matched = (q->owner == client);
|
|
if (matched)
|
|
q->klocked = 1;
|
|
spin_unlock_irqrestore(&q->owner_lock, flags);
|
|
if (matched) {
|
|
if (q->timer->running)
|
|
snd_seq_timer_stop(q->timer);
|
|
snd_seq_timer_reset(q->timer);
|
|
}
|
|
queuefree(q);
|
|
}
|
|
}
|
|
|
|
/* final stage notification -
|
|
* remove cells for no longer exist client (for non-owned queue)
|
|
* or delete this queue (for owned queue)
|
|
*/
|
|
void snd_seq_queue_client_leave(int client)
|
|
{
|
|
int i;
|
|
struct snd_seq_queue *q;
|
|
|
|
/* delete own queues from queue list */
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if ((q = queue_list_remove(i, client)) != NULL)
|
|
queue_delete(q);
|
|
}
|
|
|
|
/* remove cells from existing queues -
|
|
* they are not owned by this client
|
|
*/
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if ((q = queueptr(i)) == NULL)
|
|
continue;
|
|
if (test_bit(client, q->clients_bitmap)) {
|
|
snd_seq_prioq_leave(q->tickq, client, 0);
|
|
snd_seq_prioq_leave(q->timeq, client, 0);
|
|
snd_seq_queue_use(q->queue, client, 0);
|
|
}
|
|
queuefree(q);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/* remove cells from all queues */
|
|
void snd_seq_queue_client_leave_cells(int client)
|
|
{
|
|
int i;
|
|
struct snd_seq_queue *q;
|
|
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if ((q = queueptr(i)) == NULL)
|
|
continue;
|
|
snd_seq_prioq_leave(q->tickq, client, 0);
|
|
snd_seq_prioq_leave(q->timeq, client, 0);
|
|
queuefree(q);
|
|
}
|
|
}
|
|
|
|
/* remove cells based on flush criteria */
|
|
void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info)
|
|
{
|
|
int i;
|
|
struct snd_seq_queue *q;
|
|
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if ((q = queueptr(i)) == NULL)
|
|
continue;
|
|
if (test_bit(client, q->clients_bitmap) &&
|
|
(! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) ||
|
|
q->queue == info->queue)) {
|
|
snd_seq_prioq_remove_events(q->tickq, client, info);
|
|
snd_seq_prioq_remove_events(q->timeq, client, info);
|
|
}
|
|
queuefree(q);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
/*
|
|
* send events to all subscribed ports
|
|
*/
|
|
static void queue_broadcast_event(struct snd_seq_queue *q, struct snd_seq_event *ev,
|
|
int atomic, int hop)
|
|
{
|
|
struct snd_seq_event sev;
|
|
|
|
sev = *ev;
|
|
|
|
sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS;
|
|
sev.time.tick = q->timer->tick.cur_tick;
|
|
sev.queue = q->queue;
|
|
sev.data.queue.queue = q->queue;
|
|
|
|
/* broadcast events from Timer port */
|
|
sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
|
|
sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
|
|
sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
|
|
snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop);
|
|
}
|
|
|
|
/*
|
|
* process a received queue-control event.
|
|
* this function is exported for seq_sync.c.
|
|
*/
|
|
static void snd_seq_queue_process_event(struct snd_seq_queue *q,
|
|
struct snd_seq_event *ev,
|
|
int atomic, int hop)
|
|
{
|
|
switch (ev->type) {
|
|
case SNDRV_SEQ_EVENT_START:
|
|
snd_seq_prioq_leave(q->tickq, ev->source.client, 1);
|
|
snd_seq_prioq_leave(q->timeq, ev->source.client, 1);
|
|
if (! snd_seq_timer_start(q->timer))
|
|
queue_broadcast_event(q, ev, atomic, hop);
|
|
break;
|
|
|
|
case SNDRV_SEQ_EVENT_CONTINUE:
|
|
if (! snd_seq_timer_continue(q->timer))
|
|
queue_broadcast_event(q, ev, atomic, hop);
|
|
break;
|
|
|
|
case SNDRV_SEQ_EVENT_STOP:
|
|
snd_seq_timer_stop(q->timer);
|
|
queue_broadcast_event(q, ev, atomic, hop);
|
|
break;
|
|
|
|
case SNDRV_SEQ_EVENT_TEMPO:
|
|
snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value);
|
|
queue_broadcast_event(q, ev, atomic, hop);
|
|
break;
|
|
|
|
case SNDRV_SEQ_EVENT_SETPOS_TICK:
|
|
if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) {
|
|
queue_broadcast_event(q, ev, atomic, hop);
|
|
}
|
|
break;
|
|
|
|
case SNDRV_SEQ_EVENT_SETPOS_TIME:
|
|
if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) {
|
|
queue_broadcast_event(q, ev, atomic, hop);
|
|
}
|
|
break;
|
|
case SNDRV_SEQ_EVENT_QUEUE_SKEW:
|
|
if (snd_seq_timer_set_skew(q->timer,
|
|
ev->data.queue.param.skew.value,
|
|
ev->data.queue.param.skew.base) == 0) {
|
|
queue_broadcast_event(q, ev, atomic, hop);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Queue control via timer control port:
|
|
* this function is exported as a callback of timer port.
|
|
*/
|
|
int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop)
|
|
{
|
|
struct snd_seq_queue *q;
|
|
|
|
if (snd_BUG_ON(!ev))
|
|
return -EINVAL;
|
|
q = queueptr(ev->data.queue.queue);
|
|
|
|
if (q == NULL)
|
|
return -EINVAL;
|
|
|
|
if (! queue_access_lock(q, ev->source.client)) {
|
|
queuefree(q);
|
|
return -EPERM;
|
|
}
|
|
|
|
snd_seq_queue_process_event(q, ev, atomic, hop);
|
|
|
|
queue_access_unlock(q);
|
|
queuefree(q);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------*/
|
|
|
|
#ifdef CONFIG_SND_PROC_FS
|
|
/* exported to seq_info.c */
|
|
void snd_seq_info_queues_read(struct snd_info_entry *entry,
|
|
struct snd_info_buffer *buffer)
|
|
{
|
|
int i, bpm;
|
|
struct snd_seq_queue *q;
|
|
struct snd_seq_timer *tmr;
|
|
bool locked;
|
|
int owner;
|
|
|
|
for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {
|
|
if ((q = queueptr(i)) == NULL)
|
|
continue;
|
|
|
|
tmr = q->timer;
|
|
if (tmr->tempo)
|
|
bpm = 60000000 / tmr->tempo;
|
|
else
|
|
bpm = 0;
|
|
|
|
spin_lock_irq(&q->owner_lock);
|
|
locked = q->locked;
|
|
owner = q->owner;
|
|
spin_unlock_irq(&q->owner_lock);
|
|
|
|
snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name);
|
|
snd_iprintf(buffer, "owned by client : %d\n", owner);
|
|
snd_iprintf(buffer, "lock status : %s\n", locked ? "Locked" : "Free");
|
|
snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq));
|
|
snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq));
|
|
snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped");
|
|
snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq);
|
|
snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo);
|
|
snd_iprintf(buffer, "current BPM : %d\n", bpm);
|
|
snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec);
|
|
snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick);
|
|
snd_iprintf(buffer, "\n");
|
|
queuefree(q);
|
|
}
|
|
}
|
|
#endif /* CONFIG_SND_PROC_FS */
|
|
|