755 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * ALSA sequencer Timing queue handling4 * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl>5 *6 * MAJOR CHANGES7 * Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de>8 * - Queues are allocated dynamically via ioctl.9 * - When owner client is deleted, all owned queues are deleted, too.10 * - Owner of unlocked queue is kept unmodified even if it is11 * manipulated by other clients.12 * - Owner field in SET_QUEUE_OWNER ioctl must be identical with the13 * caller client. i.e. Changing owner to a third client is not14 * allowed.15 *16 * Aug. 30, 2000 Takashi Iwai17 * - Queues are managed in static array again, but with better way.18 * The API itself is identical.19 * - The queue is locked when struct snd_seq_queue pointer is returned via20 * queueptr(). This pointer *MUST* be released afterward by21 * queuefree(ptr).22 * - Addition of experimental sync support.23 */24 25#include <linux/init.h>26#include <linux/slab.h>27#include <sound/core.h>28 29#include "seq_memory.h"30#include "seq_queue.h"31#include "seq_clientmgr.h"32#include "seq_fifo.h"33#include "seq_timer.h"34#include "seq_info.h"35 36/* list of allocated queues */37static struct snd_seq_queue *queue_list[SNDRV_SEQ_MAX_QUEUES];38static DEFINE_SPINLOCK(queue_list_lock);39/* number of queues allocated */40static int num_queues;41 42int snd_seq_queue_get_cur_queues(void)43{44 return num_queues;45}46 47/*----------------------------------------------------------------*/48 49/* assign queue id and insert to list */50static int queue_list_add(struct snd_seq_queue *q)51{52 int i;53 54 guard(spinlock_irqsave)(&queue_list_lock);55 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {56 if (! queue_list[i]) {57 queue_list[i] = q;58 q->queue = i;59 num_queues++;60 return i;61 }62 }63 return -1;64}65 66static struct snd_seq_queue *queue_list_remove(int id, int client)67{68 struct snd_seq_queue *q;69 70 guard(spinlock_irqsave)(&queue_list_lock);71 q = queue_list[id];72 if (q) {73 guard(spinlock)(&q->owner_lock);74 if (q->owner == client) {75 /* found */76 q->klocked = 1;77 queue_list[id] = NULL;78 num_queues--;79 return q;80 }81 }82 return NULL;83}84 85/*----------------------------------------------------------------*/86 87/* create new queue (constructor) */88static struct snd_seq_queue *queue_new(int owner, int locked)89{90 struct snd_seq_queue *q;91 92 q = kzalloc(sizeof(*q), GFP_KERNEL);93 if (!q)94 return NULL;95 96 spin_lock_init(&q->owner_lock);97 spin_lock_init(&q->check_lock);98 mutex_init(&q->timer_mutex);99 snd_use_lock_init(&q->use_lock);100 q->queue = -1;101 102 q->tickq = snd_seq_prioq_new();103 q->timeq = snd_seq_prioq_new();104 q->timer = snd_seq_timer_new();105 if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) {106 snd_seq_prioq_delete(&q->tickq);107 snd_seq_prioq_delete(&q->timeq);108 snd_seq_timer_delete(&q->timer);109 kfree(q);110 return NULL;111 }112 113 q->owner = owner;114 q->locked = locked;115 q->klocked = 0;116 117 return q;118}119 120/* delete queue (destructor) */121static void queue_delete(struct snd_seq_queue *q)122{123 /* stop and release the timer */124 mutex_lock(&q->timer_mutex);125 snd_seq_timer_stop(q->timer);126 snd_seq_timer_close(q);127 mutex_unlock(&q->timer_mutex);128 /* wait until access free */129 snd_use_lock_sync(&q->use_lock);130 /* release resources... */131 snd_seq_prioq_delete(&q->tickq);132 snd_seq_prioq_delete(&q->timeq);133 snd_seq_timer_delete(&q->timer);134 135 kfree(q);136}137 138 139/*----------------------------------------------------------------*/140 141/* delete all existing queues */142void snd_seq_queues_delete(void)143{144 int i;145 146 /* clear list */147 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {148 if (queue_list[i])149 queue_delete(queue_list[i]);150 }151}152 153static void queue_use(struct snd_seq_queue *queue, int client, int use);154 155/* allocate a new queue -156 * return pointer to new queue or ERR_PTR(-errno) for error157 * The new queue's use_lock is set to 1. It is the caller's responsibility to158 * call snd_use_lock_free(&q->use_lock).159 */160struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int info_flags)161{162 struct snd_seq_queue *q;163 164 q = queue_new(client, locked);165 if (q == NULL)166 return ERR_PTR(-ENOMEM);167 q->info_flags = info_flags;168 queue_use(q, client, 1);169 snd_use_lock_use(&q->use_lock);170 if (queue_list_add(q) < 0) {171 snd_use_lock_free(&q->use_lock);172 queue_delete(q);173 return ERR_PTR(-ENOMEM);174 }175 return q;176}177 178/* delete a queue - queue must be owned by the client */179int snd_seq_queue_delete(int client, int queueid)180{181 struct snd_seq_queue *q;182 183 if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)184 return -EINVAL;185 q = queue_list_remove(queueid, client);186 if (q == NULL)187 return -EINVAL;188 queue_delete(q);189 190 return 0;191}192 193 194/* return pointer to queue structure for specified id */195struct snd_seq_queue *queueptr(int queueid)196{197 struct snd_seq_queue *q;198 199 if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES)200 return NULL;201 guard(spinlock_irqsave)(&queue_list_lock);202 q = queue_list[queueid];203 if (q)204 snd_use_lock_use(&q->use_lock);205 return q;206}207 208/* return the (first) queue matching with the specified name */209struct snd_seq_queue *snd_seq_queue_find_name(char *name)210{211 int i;212 struct snd_seq_queue *q;213 214 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {215 q = queueptr(i);216 if (q) {217 if (strncmp(q->name, name, sizeof(q->name)) == 0)218 return q;219 queuefree(q);220 }221 }222 return NULL;223}224 225 226/* -------------------------------------------------------- */227 228#define MAX_CELL_PROCESSES_IN_QUEUE 1000229 230void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop)231{232 struct snd_seq_event_cell *cell;233 snd_seq_tick_time_t cur_tick;234 snd_seq_real_time_t cur_time;235 int processed = 0;236 237 if (q == NULL)238 return;239 240 /* make this function non-reentrant */241 scoped_guard(spinlock_irqsave, &q->check_lock) {242 if (q->check_blocked) {243 q->check_again = 1;244 return; /* other thread is already checking queues */245 }246 q->check_blocked = 1;247 }248 249 __again:250 /* Process tick queue... */251 cur_tick = snd_seq_timer_get_cur_tick(q->timer);252 for (;;) {253 cell = snd_seq_prioq_cell_out(q->tickq, &cur_tick);254 if (!cell)255 break;256 snd_seq_dispatch_event(cell, atomic, hop);257 if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE)258 goto out; /* the rest processed at the next batch */259 }260 261 /* Process time queue... */262 cur_time = snd_seq_timer_get_cur_time(q->timer, false);263 for (;;) {264 cell = snd_seq_prioq_cell_out(q->timeq, &cur_time);265 if (!cell)266 break;267 snd_seq_dispatch_event(cell, atomic, hop);268 if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE)269 goto out; /* the rest processed at the next batch */270 }271 272 out:273 /* free lock */274 scoped_guard(spinlock_irqsave, &q->check_lock) {275 if (q->check_again) {276 q->check_again = 0;277 if (processed < MAX_CELL_PROCESSES_IN_QUEUE)278 goto __again;279 }280 q->check_blocked = 0;281 }282}283 284 285/* enqueue a event to singe queue */286int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop)287{288 int dest, err;289 struct snd_seq_queue *q;290 291 if (snd_BUG_ON(!cell))292 return -EINVAL;293 dest = cell->event.queue; /* destination queue */294 q = queueptr(dest);295 if (q == NULL)296 return -EINVAL;297 /* handle relative time stamps, convert them into absolute */298 if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) {299 switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {300 case SNDRV_SEQ_TIME_STAMP_TICK:301 cell->event.time.tick += q->timer->tick.cur_tick;302 break;303 304 case SNDRV_SEQ_TIME_STAMP_REAL:305 snd_seq_inc_real_time(&cell->event.time.time,306 &q->timer->cur_time);307 break;308 }309 cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK;310 cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS;311 }312 /* enqueue event in the real-time or midi queue */313 switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) {314 case SNDRV_SEQ_TIME_STAMP_TICK:315 err = snd_seq_prioq_cell_in(q->tickq, cell);316 break;317 318 case SNDRV_SEQ_TIME_STAMP_REAL:319 default:320 err = snd_seq_prioq_cell_in(q->timeq, cell);321 break;322 }323 324 if (err < 0) {325 queuefree(q); /* unlock */326 return err;327 }328 329 /* trigger dispatching */330 snd_seq_check_queue(q, atomic, hop);331 332 queuefree(q); /* unlock */333 334 return 0;335}336 337 338/*----------------------------------------------------------------*/339 340static inline int check_access(struct snd_seq_queue *q, int client)341{342 return (q->owner == client) || (!q->locked && !q->klocked);343}344 345/* check if the client has permission to modify queue parameters.346 * if it does, lock the queue347 */348static int queue_access_lock(struct snd_seq_queue *q, int client)349{350 int access_ok;351 352 guard(spinlock_irqsave)(&q->owner_lock);353 access_ok = check_access(q, client);354 if (access_ok)355 q->klocked = 1;356 return access_ok;357}358 359/* unlock the queue */360static inline void queue_access_unlock(struct snd_seq_queue *q)361{362 guard(spinlock_irqsave)(&q->owner_lock);363 q->klocked = 0;364}365 366/* exported - only checking permission */367int snd_seq_queue_check_access(int queueid, int client)368{369 struct snd_seq_queue *q = queueptr(queueid);370 int access_ok;371 372 if (! q)373 return 0;374 scoped_guard(spinlock_irqsave, &q->owner_lock)375 access_ok = check_access(q, client);376 queuefree(q);377 return access_ok;378}379 380/*----------------------------------------------------------------*/381 382/*383 * change queue's owner and permission384 */385int snd_seq_queue_set_owner(int queueid, int client, int locked)386{387 struct snd_seq_queue *q = queueptr(queueid);388 389 if (q == NULL)390 return -EINVAL;391 392 if (! queue_access_lock(q, client)) {393 queuefree(q);394 return -EPERM;395 }396 397 scoped_guard(spinlock_irqsave, &q->owner_lock) {398 q->locked = locked ? 1 : 0;399 q->owner = client;400 }401 queue_access_unlock(q);402 queuefree(q);403 404 return 0;405}406 407 408/*----------------------------------------------------------------*/409 410/* open timer -411 * q->use mutex should be down before calling this function to avoid412 * confliction with snd_seq_queue_use()413 */414int snd_seq_queue_timer_open(int queueid)415{416 int result = 0;417 struct snd_seq_queue *queue;418 struct snd_seq_timer *tmr;419 420 queue = queueptr(queueid);421 if (queue == NULL)422 return -EINVAL;423 tmr = queue->timer;424 result = snd_seq_timer_open(queue);425 if (result < 0) {426 snd_seq_timer_defaults(tmr);427 result = snd_seq_timer_open(queue);428 }429 queuefree(queue);430 return result;431}432 433/* close timer -434 * q->use mutex should be down before calling this function435 */436int snd_seq_queue_timer_close(int queueid)437{438 struct snd_seq_queue *queue;439 int result = 0;440 441 queue = queueptr(queueid);442 if (queue == NULL)443 return -EINVAL;444 snd_seq_timer_close(queue);445 queuefree(queue);446 return result;447}448 449/* change queue tempo and ppq */450int snd_seq_queue_timer_set_tempo(int queueid, int client,451 struct snd_seq_queue_tempo *info)452{453 struct snd_seq_queue *q = queueptr(queueid);454 int result;455 456 if (q == NULL)457 return -EINVAL;458 if (! queue_access_lock(q, client)) {459 queuefree(q);460 return -EPERM;461 }462 463 result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq,464 info->tempo_base);465 if (result >= 0 && info->skew_base > 0)466 result = snd_seq_timer_set_skew(q->timer, info->skew_value,467 info->skew_base);468 queue_access_unlock(q);469 queuefree(q);470 return result;471}472 473/* use or unuse this queue */474static void queue_use(struct snd_seq_queue *queue, int client, int use)475{476 if (use) {477 if (!test_and_set_bit(client, queue->clients_bitmap))478 queue->clients++;479 } else {480 if (test_and_clear_bit(client, queue->clients_bitmap))481 queue->clients--;482 }483 if (queue->clients) {484 if (use && queue->clients == 1)485 snd_seq_timer_defaults(queue->timer);486 snd_seq_timer_open(queue);487 } else {488 snd_seq_timer_close(queue);489 }490}491 492/* use or unuse this queue -493 * if it is the first client, starts the timer.494 * if it is not longer used by any clients, stop the timer.495 */496int snd_seq_queue_use(int queueid, int client, int use)497{498 struct snd_seq_queue *queue;499 500 queue = queueptr(queueid);501 if (queue == NULL)502 return -EINVAL;503 mutex_lock(&queue->timer_mutex);504 queue_use(queue, client, use);505 mutex_unlock(&queue->timer_mutex);506 queuefree(queue);507 return 0;508}509 510/*511 * check if queue is used by the client512 * return negative value if the queue is invalid.513 * return 0 if not used, 1 if used.514 */515int snd_seq_queue_is_used(int queueid, int client)516{517 struct snd_seq_queue *q;518 int result;519 520 q = queueptr(queueid);521 if (q == NULL)522 return -EINVAL; /* invalid queue */523 result = test_bit(client, q->clients_bitmap) ? 1 : 0;524 queuefree(q);525 return result;526}527 528 529/*----------------------------------------------------------------*/530 531/* final stage notification -532 * remove cells for no longer exist client (for non-owned queue)533 * or delete this queue (for owned queue)534 */535void snd_seq_queue_client_leave(int client)536{537 int i;538 struct snd_seq_queue *q;539 540 /* delete own queues from queue list */541 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {542 q = queue_list_remove(i, client);543 if (q)544 queue_delete(q);545 }546 547 /* remove cells from existing queues -548 * they are not owned by this client549 */550 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {551 q = queueptr(i);552 if (!q)553 continue;554 if (test_bit(client, q->clients_bitmap)) {555 snd_seq_prioq_leave(q->tickq, client, 0);556 snd_seq_prioq_leave(q->timeq, client, 0);557 snd_seq_queue_use(q->queue, client, 0);558 }559 queuefree(q);560 }561}562 563 564 565/*----------------------------------------------------------------*/566 567/* remove cells from all queues */568void snd_seq_queue_client_leave_cells(int client)569{570 int i;571 struct snd_seq_queue *q;572 573 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {574 q = queueptr(i);575 if (!q)576 continue;577 snd_seq_prioq_leave(q->tickq, client, 0);578 snd_seq_prioq_leave(q->timeq, client, 0);579 queuefree(q);580 }581}582 583/* remove cells based on flush criteria */584void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info)585{586 int i;587 struct snd_seq_queue *q;588 589 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {590 q = queueptr(i);591 if (!q)592 continue;593 if (test_bit(client, q->clients_bitmap) &&594 (! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) ||595 q->queue == info->queue)) {596 snd_seq_prioq_remove_events(q->tickq, client, info);597 snd_seq_prioq_remove_events(q->timeq, client, info);598 }599 queuefree(q);600 }601}602 603/*----------------------------------------------------------------*/604 605/*606 * send events to all subscribed ports607 */608static void queue_broadcast_event(struct snd_seq_queue *q, struct snd_seq_event *ev,609 int atomic, int hop)610{611 struct snd_seq_event sev;612 613 sev = *ev;614 615 sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS;616 sev.time.tick = q->timer->tick.cur_tick;617 sev.queue = q->queue;618 sev.data.queue.queue = q->queue;619 620 /* broadcast events from Timer port */621 sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;622 sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;623 sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;624 snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop);625}626 627/*628 * process a received queue-control event.629 * this function is exported for seq_sync.c.630 */631static void snd_seq_queue_process_event(struct snd_seq_queue *q,632 struct snd_seq_event *ev,633 int atomic, int hop)634{635 switch (ev->type) {636 case SNDRV_SEQ_EVENT_START:637 snd_seq_prioq_leave(q->tickq, ev->source.client, 1);638 snd_seq_prioq_leave(q->timeq, ev->source.client, 1);639 if (! snd_seq_timer_start(q->timer))640 queue_broadcast_event(q, ev, atomic, hop);641 break;642 643 case SNDRV_SEQ_EVENT_CONTINUE:644 if (! snd_seq_timer_continue(q->timer))645 queue_broadcast_event(q, ev, atomic, hop);646 break;647 648 case SNDRV_SEQ_EVENT_STOP:649 snd_seq_timer_stop(q->timer);650 queue_broadcast_event(q, ev, atomic, hop);651 break;652 653 case SNDRV_SEQ_EVENT_TEMPO:654 snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value);655 queue_broadcast_event(q, ev, atomic, hop);656 break;657 658 case SNDRV_SEQ_EVENT_SETPOS_TICK:659 if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) {660 queue_broadcast_event(q, ev, atomic, hop);661 }662 break;663 664 case SNDRV_SEQ_EVENT_SETPOS_TIME:665 if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) {666 queue_broadcast_event(q, ev, atomic, hop);667 }668 break;669 case SNDRV_SEQ_EVENT_QUEUE_SKEW:670 if (snd_seq_timer_set_skew(q->timer,671 ev->data.queue.param.skew.value,672 ev->data.queue.param.skew.base) == 0) {673 queue_broadcast_event(q, ev, atomic, hop);674 }675 break;676 }677}678 679 680/*681 * Queue control via timer control port:682 * this function is exported as a callback of timer port.683 */684int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop)685{686 struct snd_seq_queue *q;687 688 if (snd_BUG_ON(!ev))689 return -EINVAL;690 q = queueptr(ev->data.queue.queue);691 692 if (q == NULL)693 return -EINVAL;694 695 if (! queue_access_lock(q, ev->source.client)) {696 queuefree(q);697 return -EPERM;698 }699 700 snd_seq_queue_process_event(q, ev, atomic, hop);701 702 queue_access_unlock(q);703 queuefree(q);704 return 0;705}706 707 708/*----------------------------------------------------------------*/709 710#ifdef CONFIG_SND_PROC_FS711/* exported to seq_info.c */712void snd_seq_info_queues_read(struct snd_info_entry *entry, 713 struct snd_info_buffer *buffer)714{715 int i, bpm;716 struct snd_seq_queue *q;717 struct snd_seq_timer *tmr;718 bool locked;719 int owner;720 721 for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) {722 q = queueptr(i);723 if (!q)724 continue;725 726 tmr = q->timer;727 if (tmr->tempo)728 bpm = (60000 * tmr->tempo_base) / tmr->tempo;729 else730 bpm = 0;731 732 scoped_guard(spinlock_irq, &q->owner_lock) {733 locked = q->locked;734 owner = q->owner;735 }736 737 snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name);738 snd_iprintf(buffer, "owned by client : %d\n", owner);739 snd_iprintf(buffer, "lock status : %s\n", locked ? "Locked" : "Free");740 snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq));741 snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq));742 snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped");743 snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq);744 snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo);745 snd_iprintf(buffer, "tempo base : %d ns\n", tmr->tempo_base);746 snd_iprintf(buffer, "current BPM : %d\n", bpm);747 snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec);748 snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick);749 snd_iprintf(buffer, "\n");750 queuefree(q);751 }752}753#endif /* CONFIG_SND_PROC_FS */754 755