seq_clientmgr.c 65.8 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3
/*
 *  ALSA sequencer Client Manager
 *  Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl>
4
 *                             Jaroslav Kysela <perex@perex.cz>
Linus Torvalds's avatar
Linus Torvalds committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *                             Takashi Iwai <tiwai@suse.de>
 *
 *
 *   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 <linux/init.h>
25
#include <linux/export.h>
Linus Torvalds's avatar
Linus Torvalds committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <linux/kmod.h>

#include <sound/seq_kernel.h>
#include "seq_clientmgr.h"
#include "seq_memory.h"
#include "seq_queue.h"
#include "seq_timer.h"
#include "seq_info.h"
#include "seq_system.h"
#include <sound/seq_device.h>
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
#endif

/* Client Manager

 * this module handles the connections of userland and kernel clients
 * 
 */

49 50 51 52 53 54 55 56 57 58 59 60 61 62
/*
 * There are four ranges of client numbers (last two shared):
 * 0..15: global clients
 * 16..127: statically allocated client numbers for cards 0..27
 * 128..191: dynamically allocated client numbers for cards 28..31
 * 128..191: dynamically allocated client numbers for applications
 */

/* number of kernel non-card clients */
#define SNDRV_SEQ_GLOBAL_CLIENTS	16
/* clients per cards, for static clients */
#define SNDRV_SEQ_CLIENTS_PER_CARD	4
/* dynamically allocated client numbers (both kernel drivers and user space) */
#define SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN	128
63

Linus Torvalds's avatar
Linus Torvalds committed
64 65 66 67 68
#define SNDRV_SEQ_LFLG_INPUT	0x0001
#define SNDRV_SEQ_LFLG_OUTPUT	0x0002
#define SNDRV_SEQ_LFLG_OPEN	(SNDRV_SEQ_LFLG_INPUT|SNDRV_SEQ_LFLG_OUTPUT)

static DEFINE_SPINLOCK(clients_lock);
69
static DEFINE_MUTEX(register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
70 71 72 73 74

/*
 * client table
 */
static char clienttablock[SNDRV_SEQ_MAX_CLIENTS];
75 76
static struct snd_seq_client *clienttab[SNDRV_SEQ_MAX_CLIENTS];
static struct snd_seq_usage client_usage;
Linus Torvalds's avatar
Linus Torvalds committed
77 78 79 80

/*
 * prototypes
 */
81 82 83 84 85 86
static int bounce_error_event(struct snd_seq_client *client,
			      struct snd_seq_event *event,
			      int err, int atomic, int hop);
static int snd_seq_deliver_single_event(struct snd_seq_client *client,
					struct snd_seq_event *event,
					int filter, int atomic, int hop);
Linus Torvalds's avatar
Linus Torvalds committed
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101

/*
 */
static inline unsigned short snd_seq_file_flags(struct file *file)
{
        switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) {
        case FMODE_WRITE:
                return SNDRV_SEQ_LFLG_OUTPUT;
        case FMODE_READ:
                return SNDRV_SEQ_LFLG_INPUT;
        default:
                return SNDRV_SEQ_LFLG_OPEN;
        }
}

102
static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client)
Linus Torvalds's avatar
Linus Torvalds committed
103 104 105 106 107
{
	return snd_seq_total_cells(client->pool) > 0;
}

/* return pointer to client structure for specified id */
108
static struct snd_seq_client *clientptr(int clientid)
Linus Torvalds's avatar
Linus Torvalds committed
109 110
{
	if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
111
		pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n",
112
			   clientid);
Linus Torvalds's avatar
Linus Torvalds committed
113 114 115 116 117
		return NULL;
	}
	return clienttab[clientid];
}

118
struct snd_seq_client *snd_seq_client_use_ptr(int clientid)
Linus Torvalds's avatar
Linus Torvalds committed
119 120
{
	unsigned long flags;
121
	struct snd_seq_client *client;
Linus Torvalds's avatar
Linus Torvalds committed
122 123

	if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {
124
		pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n",
125
			   clientid);
Linus Torvalds's avatar
Linus Torvalds committed
126 127 128 129 130 131 132 133 134 135 136
		return NULL;
	}
	spin_lock_irqsave(&clients_lock, flags);
	client = clientptr(clientid);
	if (client)
		goto __lock;
	if (clienttablock[clientid]) {
		spin_unlock_irqrestore(&clients_lock, flags);
		return NULL;
	}
	spin_unlock_irqrestore(&clients_lock, flags);
137
#ifdef CONFIG_MODULES
138
	if (!in_interrupt()) {
139
		static char client_requested[SNDRV_SEQ_GLOBAL_CLIENTS];
Linus Torvalds's avatar
Linus Torvalds committed
140
		static char card_requested[SNDRV_CARDS];
141
		if (clientid < SNDRV_SEQ_GLOBAL_CLIENTS) {
Linus Torvalds's avatar
Linus Torvalds committed
142 143
			int idx;
			
144
			if (!client_requested[clientid]) {
Linus Torvalds's avatar
Linus Torvalds committed
145
				client_requested[clientid] = 1;
146
				for (idx = 0; idx < 15; idx++) {
Linus Torvalds's avatar
Linus Torvalds committed
147 148 149
					if (seq_client_load[idx] < 0)
						break;
					if (seq_client_load[idx] == clientid) {
150 151
						request_module("snd-seq-client-%i",
							       clientid);
Linus Torvalds's avatar
Linus Torvalds committed
152 153 154 155
						break;
					}
				}
			}
156 157 158
		} else if (clientid < SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) {
			int card = (clientid - SNDRV_SEQ_GLOBAL_CLIENTS) /
				SNDRV_SEQ_CLIENTS_PER_CARD;
Linus Torvalds's avatar
Linus Torvalds committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
			if (card < snd_ecards_limit) {
				if (! card_requested[card]) {
					card_requested[card] = 1;
					snd_request_card(card);
				}
				snd_seq_device_load_drivers();
			}
		}
		spin_lock_irqsave(&clients_lock, flags);
		client = clientptr(clientid);
		if (client)
			goto __lock;
		spin_unlock_irqrestore(&clients_lock, flags);
	}
#endif
	return NULL;

      __lock:
	snd_use_lock_use(&client->use_lock);
	spin_unlock_irqrestore(&clients_lock, flags);
	return client;
}

182
static void usage_alloc(struct snd_seq_usage *res, int num)
Linus Torvalds's avatar
Linus Torvalds committed
183 184 185 186 187 188
{
	res->cur += num;
	if (res->cur > res->peak)
		res->peak = res->cur;
}

189
static void usage_free(struct snd_seq_usage *res, int num)
Linus Torvalds's avatar
Linus Torvalds committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203
{
	res->cur -= num;
}

/* initialise data structures */
int __init client_init_data(void)
{
	/* zap out the client table */
	memset(&clienttablock, 0, sizeof(clienttablock));
	memset(&clienttab, 0, sizeof(clienttab));
	return 0;
}


204
static struct snd_seq_client *seq_create_client1(int client_index, int poolsize)
Linus Torvalds's avatar
Linus Torvalds committed
205 206 207
{
	unsigned long flags;
	int c;
208
	struct snd_seq_client *client;
Linus Torvalds's avatar
Linus Torvalds committed
209 210

	/* init client data */
211
	client = kzalloc(sizeof(*client), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
212 213 214 215 216 217 218 219 220 221
	if (client == NULL)
		return NULL;
	client->pool = snd_seq_pool_new(poolsize);
	if (client->pool == NULL) {
		kfree(client);
		return NULL;
	}
	client->type = NO_CLIENT;
	snd_use_lock_init(&client->use_lock);
	rwlock_init(&client->ports_lock);
222
	mutex_init(&client->ports_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
223
	INIT_LIST_HEAD(&client->ports_list_head);
224
	mutex_init(&client->ioctl_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
225 226 227 228

	/* find free slot in the client table */
	spin_lock_irqsave(&clients_lock, flags);
	if (client_index < 0) {
229 230 231
		for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN;
		     c < SNDRV_SEQ_MAX_CLIENTS;
		     c++) {
Linus Torvalds's avatar
Linus Torvalds committed
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
			if (clienttab[c] || clienttablock[c])
				continue;
			clienttab[client->number = c] = client;
			spin_unlock_irqrestore(&clients_lock, flags);
			return client;
		}
	} else {
		if (clienttab[client_index] == NULL && !clienttablock[client_index]) {
			clienttab[client->number = client_index] = client;
			spin_unlock_irqrestore(&clients_lock, flags);
			return client;
		}
	}
	spin_unlock_irqrestore(&clients_lock, flags);
	snd_seq_pool_delete(&client->pool);
	kfree(client);
	return NULL;	/* no free slot found or busy, return failure code */
}


252
static int seq_free_client1(struct snd_seq_client *client)
Linus Torvalds's avatar
Linus Torvalds committed
253 254 255
{
	unsigned long flags;

256 257
	if (!client)
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
	snd_seq_delete_all_ports(client);
	snd_seq_queue_client_leave(client->number);
	spin_lock_irqsave(&clients_lock, flags);
	clienttablock[client->number] = 1;
	clienttab[client->number] = NULL;
	spin_unlock_irqrestore(&clients_lock, flags);
	snd_use_lock_sync(&client->use_lock);
	snd_seq_queue_client_termination(client->number);
	if (client->pool)
		snd_seq_pool_delete(&client->pool);
	spin_lock_irqsave(&clients_lock, flags);
	clienttablock[client->number] = 0;
	spin_unlock_irqrestore(&clients_lock, flags);
	return 0;
}


275
static void seq_free_client(struct snd_seq_client * client)
Linus Torvalds's avatar
Linus Torvalds committed
276
{
277
	mutex_lock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
278 279
	switch (client->type) {
	case NO_CLIENT:
280 281
		pr_warn("ALSA: seq: Trying to free unused client %d\n",
			client->number);
Linus Torvalds's avatar
Linus Torvalds committed
282 283 284 285 286 287 288 289
		break;
	case USER_CLIENT:
	case KERNEL_CLIENT:
		seq_free_client1(client);
		usage_free(&client_usage, 1);
		break;

	default:
290
		pr_err("ALSA: seq: Trying to free client %d with undefined type = %d\n",
291
			   client->number, client->type);
Linus Torvalds's avatar
Linus Torvalds committed
292
	}
293
	mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
294 295 296 297 298 299 300 301 302 303 304 305

	snd_seq_system_client_ev_client_exit(client->number);
}



/* -------------------------------------------------------- */

/* create a user client */
static int snd_seq_open(struct inode *inode, struct file *file)
{
	int c, mode;			/* client id */
306 307
	struct snd_seq_client *client;
	struct snd_seq_user_client *user;
308 309 310 311 312
	int err;

	err = nonseekable_open(inode, file);
	if (err < 0)
		return err;
Linus Torvalds's avatar
Linus Torvalds committed
313

314
	if (mutex_lock_interruptible(&register_mutex))
Linus Torvalds's avatar
Linus Torvalds committed
315
		return -ERESTARTSYS;
316
	client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS);
Linus Torvalds's avatar
Linus Torvalds committed
317
	if (client == NULL) {
318
		mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
		return -ENOMEM;	/* failure code */
	}

	mode = snd_seq_file_flags(file);
	if (mode & SNDRV_SEQ_LFLG_INPUT)
		client->accept_input = 1;
	if (mode & SNDRV_SEQ_LFLG_OUTPUT)
		client->accept_output = 1;

	user = &client->data.user;
	user->fifo = NULL;
	user->fifo_pool_size = 0;

	if (mode & SNDRV_SEQ_LFLG_INPUT) {
		user->fifo_pool_size = SNDRV_SEQ_DEFAULT_CLIENT_EVENTS;
		user->fifo = snd_seq_fifo_new(user->fifo_pool_size);
		if (user->fifo == NULL) {
			seq_free_client1(client);
			kfree(client);
338
			mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
339 340 341 342 343 344
			return -ENOMEM;
		}
	}

	usage_alloc(&client_usage, 1);
	client->type = USER_CLIENT;
345
	mutex_unlock(&register_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
346 347 348 349 350 351 352

	c = client->number;
	file->private_data = client;

	/* fill client data */
	user->file = file;
	sprintf(client->name, "Client-%d", c);
353
	client->data.user.owner = get_pid(task_pid(current));
Linus Torvalds's avatar
Linus Torvalds committed
354 355 356 357 358 359 360 361 362 363

	/* make others aware this new client */
	snd_seq_system_client_ev_client_start(c);

	return 0;
}

/* delete a user client */
static int snd_seq_release(struct inode *inode, struct file *file)
{
364
	struct snd_seq_client *client = file->private_data;
Linus Torvalds's avatar
Linus Torvalds committed
365 366 367 368 369

	if (client) {
		seq_free_client(client);
		if (client->data.user.fifo)
			snd_seq_fifo_delete(&client->data.user.fifo);
370
		put_pid(client->data.user.owner);
Linus Torvalds's avatar
Linus Torvalds committed
371 372 373 374 375 376 377 378 379 380 381 382 383 384
		kfree(client);
	}

	return 0;
}


/* handle client read() */
/* possible error values:
 *	-ENXIO	invalid client or file open mode
 *	-ENOSPC	FIFO overflow (the flag is cleared after this error report)
 *	-EINVAL	no enough user-space buffer to write the whole event
 *	-EFAULT	seg. fault during copy to user space
 */
385 386
static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,
			    loff_t *offset)
Linus Torvalds's avatar
Linus Torvalds committed
387
{
388 389
	struct snd_seq_client *client = file->private_data;
	struct snd_seq_fifo *fifo;
Linus Torvalds's avatar
Linus Torvalds committed
390 391
	int err;
	long result = 0;
392
	struct snd_seq_event_cell *cell;
Linus Torvalds's avatar
Linus Torvalds committed
393 394 395 396 397 398 399 400

	if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT))
		return -ENXIO;

	if (!access_ok(VERIFY_WRITE, buf, count))
		return -EFAULT;

	/* check client structures are in place */
401 402
	if (snd_BUG_ON(!client))
		return -ENXIO;
Linus Torvalds's avatar
Linus Torvalds committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418

	if (!client->accept_input || (fifo = client->data.user.fifo) == NULL)
		return -ENXIO;

	if (atomic_read(&fifo->overflow) > 0) {
		/* buffer overflow is detected */
		snd_seq_fifo_clear(fifo);
		/* return error code */
		return -ENOSPC;
	}

	cell = NULL;
	err = 0;
	snd_seq_fifo_lock(fifo);

	/* while data available in queue */
419
	while (count >= sizeof(struct snd_seq_event)) {
Linus Torvalds's avatar
Linus Torvalds committed
420 421 422 423 424 425 426
		int nonblock;

		nonblock = (file->f_flags & O_NONBLOCK) || result > 0;
		if ((err = snd_seq_fifo_cell_out(fifo, &cell, nonblock)) < 0) {
			break;
		}
		if (snd_seq_ev_is_variable(&cell->event)) {
427
			struct snd_seq_event tmpev;
Linus Torvalds's avatar
Linus Torvalds committed
428 429
			tmpev = cell->event;
			tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;
430
			if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) {
Linus Torvalds's avatar
Linus Torvalds committed
431 432 433
				err = -EFAULT;
				break;
			}
434 435
			count -= sizeof(struct snd_seq_event);
			buf += sizeof(struct snd_seq_event);
436 437
			err = snd_seq_expand_var_event(&cell->event, count,
						       (char __force *)buf, 0,
438
						       sizeof(struct snd_seq_event));
Linus Torvalds's avatar
Linus Torvalds committed
439 440 441 442 443 444
			if (err < 0)
				break;
			result += err;
			count -= err;
			buf += err;
		} else {
445
			if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) {
Linus Torvalds's avatar
Linus Torvalds committed
446 447 448
				err = -EFAULT;
				break;
			}
449 450
			count -= sizeof(struct snd_seq_event);
			buf += sizeof(struct snd_seq_event);
Linus Torvalds's avatar
Linus Torvalds committed
451 452 453
		}
		snd_seq_cell_free(cell);
		cell = NULL; /* to be sure */
454
		result += sizeof(struct snd_seq_event);
Linus Torvalds's avatar
Linus Torvalds committed
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
	}

	if (err < 0) {
		if (cell)
			snd_seq_fifo_cell_putback(fifo, cell);
		if (err == -EAGAIN && result > 0)
			err = 0;
	}
	snd_seq_fifo_unlock(fifo);

	return (err < 0) ? err : result;
}


/*
 * check access permission to the port
 */
472
static int check_port_perm(struct snd_seq_client_port *port, unsigned int flags)
Linus Torvalds's avatar
Linus Torvalds committed
473 474 475 476 477 478 479 480 481 482
{
	if ((port->capability & flags) != flags)
		return 0;
	return flags;
}

/*
 * check if the destination client is available, and return the pointer
 * if filter is non-zero, client filter bitmap is tested.
 */
483 484
static struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event,
						    int filter)
Linus Torvalds's avatar
Linus Torvalds committed
485
{
486
	struct snd_seq_client *dest;
Linus Torvalds's avatar
Linus Torvalds committed
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516

	dest = snd_seq_client_use_ptr(event->dest.client);
	if (dest == NULL)
		return NULL;
	if (! dest->accept_input)
		goto __not_avail;
	if ((dest->filter & SNDRV_SEQ_FILTER_USE_EVENT) &&
	    ! test_bit(event->type, dest->event_filter))
		goto __not_avail;
	if (filter && !(dest->filter & filter))
		goto __not_avail;

	return dest; /* ok - accessible */
__not_avail:
	snd_seq_client_unlock(dest);
	return NULL;
}


/*
 * Return the error event.
 *
 * If the receiver client is a user client, the original event is
 * encapsulated in SNDRV_SEQ_EVENT_BOUNCE as variable length event.  If
 * the original event is also variable length, the external data is
 * copied after the event record. 
 * If the receiver client is a kernel client, the original event is
 * quoted in SNDRV_SEQ_EVENT_KERNEL_ERROR, since this requires no extra
 * kmalloc.
 */
517 518
static int bounce_error_event(struct snd_seq_client *client,
			      struct snd_seq_event *event,
Linus Torvalds's avatar
Linus Torvalds committed
519 520
			      int err, int atomic, int hop)
{
521
	struct snd_seq_event bounce_ev;
Linus Torvalds's avatar
Linus Torvalds committed
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
	int result;

	if (client == NULL ||
	    ! (client->filter & SNDRV_SEQ_FILTER_BOUNCE) ||
	    ! client->accept_input)
		return 0; /* ignored */

	/* set up quoted error */
	memset(&bounce_ev, 0, sizeof(bounce_ev));
	bounce_ev.type = SNDRV_SEQ_EVENT_KERNEL_ERROR;
	bounce_ev.flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
	bounce_ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
	bounce_ev.source.client = SNDRV_SEQ_CLIENT_SYSTEM;
	bounce_ev.source.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE;
	bounce_ev.dest.client = client->number;
	bounce_ev.dest.port = event->source.port;
	bounce_ev.data.quote.origin = event->dest;
	bounce_ev.data.quote.event = event;
	bounce_ev.data.quote.value = -err; /* use positive value */
	result = snd_seq_deliver_single_event(NULL, &bounce_ev, 0, atomic, hop + 1);
	if (result < 0) {
		client->event_lost++;
		return result;
	}

	return result;
}


/*
 * rewrite the time-stamp of the event record with the curren time
 * of the given queue.
 * return non-zero if updated.
 */
556 557
static int update_timestamp_of_queue(struct snd_seq_event *event,
				     int queue, int real_time)
Linus Torvalds's avatar
Linus Torvalds committed
558
{
559
	struct snd_seq_queue *q;
Linus Torvalds's avatar
Linus Torvalds committed
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584

	q = queueptr(queue);
	if (! q)
		return 0;
	event->queue = queue;
	event->flags &= ~SNDRV_SEQ_TIME_STAMP_MASK;
	if (real_time) {
		event->time.time = snd_seq_timer_get_cur_time(q->timer);
		event->flags |= SNDRV_SEQ_TIME_STAMP_REAL;
	} else {
		event->time.tick = snd_seq_timer_get_cur_tick(q->timer);
		event->flags |= SNDRV_SEQ_TIME_STAMP_TICK;
	}
	queuefree(q);
	return 1;
}


/*
 * deliver an event to the specified destination.
 * if filter is non-zero, client filter bitmap is tested.
 *
 *  RETURN VALUE: 0 : if succeeded
 *		 <0 : error
 */
585 586
static int snd_seq_deliver_single_event(struct snd_seq_client *client,
					struct snd_seq_event *event,
Linus Torvalds's avatar
Linus Torvalds committed
587 588
					int filter, int atomic, int hop)
{
589 590
	struct snd_seq_client *dest = NULL;
	struct snd_seq_client_port *dest_port = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
	int result = -ENOENT;
	int direct;

	direct = snd_seq_ev_is_direct(event);

	dest = get_event_dest_client(event, filter);
	if (dest == NULL)
		goto __skip;
	dest_port = snd_seq_port_use_ptr(dest, event->dest.port);
	if (dest_port == NULL)
		goto __skip;

	/* check permission */
	if (! check_port_perm(dest_port, SNDRV_SEQ_PORT_CAP_WRITE)) {
		result = -EPERM;
		goto __skip;
	}
		
	if (dest_port->timestamping)
		update_timestamp_of_queue(event, dest_port->time_queue,
					  dest_port->time_real);

	switch (dest->type) {
	case USER_CLIENT:
		if (dest->data.user.fifo)
			result = snd_seq_fifo_event_in(dest->data.user.fifo, event);
		break;

	case KERNEL_CLIENT:
		if (dest_port->event_input == NULL)
			break;
622 623 624
		result = dest_port->event_input(event, direct,
						dest_port->private_data,
						atomic, hop);
Linus Torvalds's avatar
Linus Torvalds committed
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
		break;
	default:
		break;
	}

  __skip:
	if (dest_port)
		snd_seq_port_unlock(dest_port);
	if (dest)
		snd_seq_client_unlock(dest);

	if (result < 0 && !direct) {
		result = bounce_error_event(client, event, result, atomic, hop);
	}
	return result;
}


/*
 * send the event to all subscribers:
 */
646 647
static int deliver_to_subscribers(struct snd_seq_client *client,
				  struct snd_seq_event *event,
Linus Torvalds's avatar
Linus Torvalds committed
648 649
				  int atomic, int hop)
{
650
	struct snd_seq_subscribers *subs;
651
	int err, result = 0, num_ev = 0;
652 653 654
	struct snd_seq_event event_saved;
	struct snd_seq_client_port *src_port;
	struct snd_seq_port_subs_info *grp;
Linus Torvalds's avatar
Linus Torvalds committed
655 656 657 658 659 660 661 662 663 664 665 666

	src_port = snd_seq_port_use_ptr(client, event->source.port);
	if (src_port == NULL)
		return -EINVAL; /* invalid source port */
	/* save original event record */
	event_saved = *event;
	grp = &src_port->c_src;
	
	/* lock list */
	if (atomic)
		read_lock(&grp->list_lock);
	else
667
		down_read_nested(&grp->list_mutex, hop);
668
	list_for_each_entry(subs, &grp->list_head, src_list) {
669 670 671
		/* both ports ready? */
		if (atomic_read(&subs->ref_count) != 2)
			continue;
Linus Torvalds's avatar
Linus Torvalds committed
672 673 674 675 676 677 678
		event->dest = subs->info.dest;
		if (subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP)
			/* convert time according to flag with subscription */
			update_timestamp_of_queue(event, subs->info.queue,
						  subs->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL);
		err = snd_seq_deliver_single_event(client, event,
						   0, atomic, hop);
679 680 681 682 683 684
		if (err < 0) {
			/* save first error that occurs and continue */
			if (!result)
				result = err;
			continue;
		}
Linus Torvalds's avatar
Linus Torvalds committed
685 686 687 688 689 690 691 692 693 694
		num_ev++;
		/* restore original event record */
		*event = event_saved;
	}
	if (atomic)
		read_unlock(&grp->list_lock);
	else
		up_read(&grp->list_mutex);
	*event = event_saved; /* restore */
	snd_seq_port_unlock(src_port);
695
	return (result < 0) ? result : num_ev;
Linus Torvalds's avatar
Linus Torvalds committed
696 697 698 699 700 701 702
}


#ifdef SUPPORT_BROADCAST 
/*
 * broadcast to all ports:
 */
703 704
static int port_broadcast_event(struct snd_seq_client *client,
				struct snd_seq_event *event,
Linus Torvalds's avatar
Linus Torvalds committed
705 706
				int atomic, int hop)
{
707
	int num_ev = 0, err, result = 0;
708
	struct snd_seq_client *dest_client;
709
	struct snd_seq_client_port *port;
Linus Torvalds's avatar
Linus Torvalds committed
710 711 712 713 714 715

	dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST);
	if (dest_client == NULL)
		return 0; /* no matching destination */

	read_lock(&dest_client->ports_lock);
716
	list_for_each_entry(port, &dest_client->ports_list_head, list) {
Linus Torvalds's avatar
Linus Torvalds committed
717 718 719 720 721
		event->dest.port = port->addr.port;
		/* pass NULL as source client to avoid error bounce */
		err = snd_seq_deliver_single_event(NULL, event,
						   SNDRV_SEQ_FILTER_BROADCAST,
						   atomic, hop);
722 723 724 725 726 727
		if (err < 0) {
			/* save first error that occurs and continue */
			if (!result)
				result = err;
			continue;
		}
Linus Torvalds's avatar
Linus Torvalds committed
728 729 730 731 732
		num_ev++;
	}
	read_unlock(&dest_client->ports_lock);
	snd_seq_client_unlock(dest_client);
	event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */
733
	return (result < 0) ? result : num_ev;
Linus Torvalds's avatar
Linus Torvalds committed
734 735 736 737 738 739
}

/*
 * send the event to all clients:
 * if destination port is also ADDRESS_BROADCAST, deliver to all ports.
 */
740 741
static int broadcast_event(struct snd_seq_client *client,
			   struct snd_seq_event *event, int atomic, int hop)
Linus Torvalds's avatar
Linus Torvalds committed
742
{
743
	int err, result = 0, num_ev = 0;
Linus Torvalds's avatar
Linus Torvalds committed
744
	int dest;
745
	struct snd_seq_addr addr;
Linus Torvalds's avatar
Linus Torvalds committed
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761

	addr = event->dest; /* save */

	for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) {
		/* don't send to itself */
		if (dest == client->number)
			continue;
		event->dest.client = dest;
		event->dest.port = addr.port;
		if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST)
			err = port_broadcast_event(client, event, atomic, hop);
		else
			/* pass NULL as source client to avoid error bounce */
			err = snd_seq_deliver_single_event(NULL, event,
							   SNDRV_SEQ_FILTER_BROADCAST,
							   atomic, hop);
762 763 764 765 766 767
		if (err < 0) {
			/* save first error that occurs and continue */
			if (!result)
				result = err;
			continue;
		}
Linus Torvalds's avatar
Linus Torvalds committed
768 769 770
		num_ev += err;
	}
	event->dest = addr; /* restore */
771
	return (result < 0) ? result : num_ev;
Linus Torvalds's avatar
Linus Torvalds committed
772 773 774 775
}


/* multicast - not supported yet */
776
static int multicast_event(struct snd_seq_client *client, struct snd_seq_event *event,
Linus Torvalds's avatar
Linus Torvalds committed
777 778
			   int atomic, int hop)
{
779
	pr_debug("ALSA: seq: multicast not supported yet.\n");
Linus Torvalds's avatar
Linus Torvalds committed
780 781 782 783 784 785 786 787 788 789 790 791 792
	return 0; /* ignored */
}
#endif /* SUPPORT_BROADCAST */


/* deliver an event to the destination port(s).
 * if the event is to subscribers or broadcast, the event is dispatched
 * to multiple targets.
 *
 * RETURN VALUE: n > 0  : the number of delivered events.
 *               n == 0 : the event was not passed to any client.
 *               n < 0  : error - event was not processed.
 */
793
static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_event *event,
Linus Torvalds's avatar
Linus Torvalds committed
794 795 796 797 798 799
				 int atomic, int hop)
{
	int result;

	hop++;
	if (hop >= SNDRV_SEQ_MAX_HOPS) {
800
		pr_debug("ALSA: seq: too long delivery path (%d:%d->%d:%d)\n",
Linus Torvalds's avatar
Linus Torvalds committed
801 802 803 804 805
			   event->source.client, event->source.port,
			   event->dest.client, event->dest.port);
		return -EMLINK;
	}

806 807 808 809
	if (snd_seq_ev_is_variable(event) &&
	    snd_BUG_ON(atomic && (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR)))
		return -EINVAL;

Linus Torvalds's avatar
Linus Torvalds committed
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
	if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS ||
	    event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS)
		result = deliver_to_subscribers(client, event, atomic, hop);
#ifdef SUPPORT_BROADCAST
	else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST ||
		 event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST)
		result = broadcast_event(client, event, atomic, hop);
	else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS)
		result = multicast_event(client, event, atomic, hop);
	else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST)
		result = port_broadcast_event(client, event, atomic, hop);
#endif
	else
		result = snd_seq_deliver_single_event(client, event, 0, atomic, hop);

	return result;
}

/*
 * dispatch an event cell:
 * This function is called only from queue check routines in timer
 * interrupts or after enqueued.
 * The event cell shall be released or re-queued in this function.
 *
 * RETURN VALUE: n > 0  : the number of delivered events.
 *		 n == 0 : the event was not passed to any client.
 *		 n < 0  : error - event was not processed.
 */
838
int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop)
Linus Torvalds's avatar
Linus Torvalds committed
839
{
840
	struct snd_seq_client *client;
Linus Torvalds's avatar
Linus Torvalds committed
841 842
	int result;

843 844
	if (snd_BUG_ON(!cell))
		return -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
845 846 847 848 849 850 851 852 853 854 855 856

	client = snd_seq_client_use_ptr(cell->event.source.client);
	if (client == NULL) {
		snd_seq_cell_free(cell); /* release this cell */
		return -EINVAL;
	}

	if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) {
		/* NOTE event:
		 * the event cell is re-used as a NOTE-OFF event and
		 * enqueued again.
		 */
857
		struct snd_seq_event tmpev, *ev;
Linus Torvalds's avatar
Linus Torvalds committed
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909

		/* reserve this event to enqueue note-off later */
		tmpev = cell->event;
		tmpev.type = SNDRV_SEQ_EVENT_NOTEON;
		result = snd_seq_deliver_event(client, &tmpev, atomic, hop);

		/*
		 * This was originally a note event.  We now re-use the
		 * cell for the note-off event.
		 */

		ev = &cell->event;
		ev->type = SNDRV_SEQ_EVENT_NOTEOFF;
		ev->flags |= SNDRV_SEQ_PRIORITY_HIGH;

		/* add the duration time */
		switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) {
		case SNDRV_SEQ_TIME_STAMP_TICK:
			ev->time.tick += ev->data.note.duration;
			break;
		case SNDRV_SEQ_TIME_STAMP_REAL:
			/* unit for duration is ms */
			ev->time.time.tv_nsec += 1000000 * (ev->data.note.duration % 1000);
			ev->time.time.tv_sec += ev->data.note.duration / 1000 +
						ev->time.time.tv_nsec / 1000000000;
			ev->time.time.tv_nsec %= 1000000000;
			break;
		}
		ev->data.note.velocity = ev->data.note.off_velocity;

		/* Now queue this cell as the note off event */
		if (snd_seq_enqueue_event(cell, atomic, hop) < 0)
			snd_seq_cell_free(cell); /* release this cell */

	} else {
		/* Normal events:
		 * event cell is freed after processing the event
		 */

		result = snd_seq_deliver_event(client, &cell->event, atomic, hop);
		snd_seq_cell_free(cell);
	}

	snd_seq_client_unlock(client);
	return result;
}


/* Allocate a cell from client pool and enqueue it to queue:
 * if pool is empty and blocking is TRUE, sleep until a new cell is
 * available.
 */
910 911
static int snd_seq_client_enqueue_event(struct snd_seq_client *client,
					struct snd_seq_event *event,
Linus Torvalds's avatar
Linus Torvalds committed
912
					struct file *file, int blocking,
913 914
					int atomic, int hop,
					struct mutex *mutexp)
Linus Torvalds's avatar
Linus Torvalds committed
915
{
916
	struct snd_seq_event_cell *cell;
Linus Torvalds's avatar
Linus Torvalds committed
917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
	int err;

	/* special queue values - force direct passing */
	if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
		event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
		event->queue = SNDRV_SEQ_QUEUE_DIRECT;
	} else
#ifdef SUPPORT_BROADCAST
		if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) {
			event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST;
			event->queue = SNDRV_SEQ_QUEUE_DIRECT;
		}
#endif
	if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) {
		/* check presence of source port */
932
		struct snd_seq_client_port *src_port = snd_seq_port_use_ptr(client, event->source.port);
Linus Torvalds's avatar
Linus Torvalds committed
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
		if (src_port == NULL)
			return -EINVAL;
		snd_seq_port_unlock(src_port);
	}

	/* direct event processing without enqueued */
	if (snd_seq_ev_is_direct(event)) {
		if (event->type == SNDRV_SEQ_EVENT_NOTE)
			return -EINVAL; /* this event must be enqueued! */
		return snd_seq_deliver_event(client, event, atomic, hop);
	}

	/* Not direct, normal queuing */
	if (snd_seq_queue_is_used(event->queue, client->number) <= 0)
		return -EINVAL;  /* invalid queue */
	if (! snd_seq_write_pool_allocated(client))
		return -ENXIO; /* queue is not allocated */

	/* allocate an event cell */
952 953
	err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic,
				file, mutexp);
Linus Torvalds's avatar
Linus Torvalds committed
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
	if (err < 0)
		return err;

	/* we got a cell. enqueue it. */
	if ((err = snd_seq_enqueue_event(cell, atomic, hop)) < 0) {
		snd_seq_cell_free(cell);
		return err;
	}

	return 0;
}


/*
 * check validity of event type and data length.
 * return non-zero if invalid.
 */
971
static int check_event_type_and_length(struct snd_seq_event *ev)
Linus Torvalds's avatar
Linus Torvalds committed
972 973 974 975 976 977 978 979 980 981 982 983
{
	switch (snd_seq_ev_length_type(ev)) {
	case SNDRV_SEQ_EVENT_LENGTH_FIXED:
		if (snd_seq_ev_is_variable_type(ev))
			return -EINVAL;
		break;
	case SNDRV_SEQ_EVENT_LENGTH_VARIABLE:
		if (! snd_seq_ev_is_variable_type(ev) ||
		    (ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK) >= SNDRV_SEQ_MAX_EVENT_LEN)
			return -EINVAL;
		break;
	case SNDRV_SEQ_EVENT_LENGTH_VARUSR:
984
		if (! snd_seq_ev_is_direct(ev))
Linus Torvalds's avatar
Linus Torvalds committed
985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
			return -EINVAL;
		break;
	}
	return 0;
}


/* handle write() */
/* possible error values:
 *	-ENXIO	invalid client or file open mode
 *	-ENOMEM	malloc failed
 *	-EFAULT	seg. fault during copy from user space
 *	-EINVAL	invalid event
 *	-EAGAIN	no space in output pool
 *	-EINTR	interrupts while sleep
 *	-EMLINK	too many hops
 *	others	depends on return value from driver callback
 */
1003 1004
static ssize_t snd_seq_write(struct file *file, const char __user *buf,
			     size_t count, loff_t *offset)
Linus Torvalds's avatar
Linus Torvalds committed
1005
{
1006
	struct snd_seq_client *client = file->private_data;
Linus Torvalds's avatar
Linus Torvalds committed
1007
	int written = 0, len;
1008
	int err;
1009
	struct snd_seq_event event;
Linus Torvalds's avatar
Linus Torvalds committed
1010 1011 1012 1013 1014

	if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT))
		return -ENXIO;

	/* check client structures are in place */
1015 1016
	if (snd_BUG_ON(!client))
		return -ENXIO;
Linus Torvalds's avatar
Linus Torvalds committed
1017 1018 1019 1020 1021
		
	if (!client->accept_output || client->pool == NULL)
		return -ENXIO;

	/* allocate the pool now if the pool is not allocated yet */ 
1022
	mutex_lock(&client->ioctl_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
1023
	if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) {
1024 1025
		err = snd_seq_pool_init(client->pool);
		if (err < 0)
1026
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
1027 1028 1029
	}

	/* only process whole events */
1030
	err = -EINVAL;
1031
	while (count >= sizeof(struct snd_seq_event)) {
Linus Torvalds's avatar
Linus Torvalds committed
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
		/* Read in the event header from the user */
		len = sizeof(event);
		if (copy_from_user(&event, buf, len)) {
			err = -EFAULT;
			break;
		}
		event.source.client = client->number;	/* fill in client number */
		/* Check for extension data length */
		if (check_event_type_and_length(&event)) {
			err = -EINVAL;
			break;
		}

		/* check for special events */
		if (event.type == SNDRV_SEQ_EVENT_NONE)
			goto __skip_event;
		else if (snd_seq_ev_is_reserved(&event)) {
			err = -EINVAL;
			break;
		}

		if (snd_seq_ev_is_variable(&event)) {
			int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK;
			if ((size_t)(extlen + len) > count) {
				/* back out, will get an error this time or next */
				err = -EINVAL;
				break;
			}
			/* set user space pointer */
			event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR;
1062
			event.data.ext.ptr = (char __force *)buf
1063
						+ sizeof(struct snd_seq_event);
Linus Torvalds's avatar
Linus Torvalds committed
1064 1065 1066 1067
			len += extlen; /* increment data length */
		} else {
#ifdef CONFIG_COMPAT
			if (client->convert32 && snd_seq_ev_is_varusr(&event)) {
1068
				void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]);
Linus Torvalds's avatar
Linus Torvalds committed
1069 1070 1071 1072 1073 1074 1075 1076
				event.data.ext.ptr = ptr;
			}
#endif
		}

		/* ok, enqueue it */
		err = snd_seq_client_enqueue_event(client, &event, file,
						   !(file->f_flags & O_NONBLOCK),
1077
						   0, 0, &client->ioctl_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
		if (err < 0)
			break;

	__skip_event:
		/* Update pointers and counts */
		count -= len;
		buf += len;
		written += len;
	}

1088 1089
 out:
	mutex_unlock(&client->ioctl_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
1090 1091 1092 1093 1094 1095 1096
	return written ? written : err;
}


/*
 * handle polling
 */
1097
static __poll_t snd_seq_poll(struct file *file, poll_table * wait)
Linus Torvalds's avatar
Linus Torvalds committed
1098
{
1099
	struct snd_seq_client *client = file->private_data;
1100
	__poll_t mask = 0;
Linus Torvalds's avatar
Linus Torvalds committed
1101 1102

	/* check client structures are in place */
1103 1104
	if (snd_BUG_ON(!client))
		return -ENXIO;
Linus Torvalds's avatar
Linus Torvalds committed
1105 1106 1107 1108 1109 1110

	if ((snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT) &&
	    client->data.user.fifo) {

		/* check if data is available in the outqueue */
		if (snd_seq_fifo_poll_wait(client->data.user.fifo, file, wait))
1111
			mask |= EPOLLIN | EPOLLRDNORM;
Linus Torvalds's avatar
Linus Torvalds committed
1112 1113 1114 1115 1116 1117 1118
	}

	if (snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT) {

		/* check if data is available in the pool */
		if (!snd_seq_write_pool_allocated(client) ||
		    snd_seq_pool_poll_wait(client->pool, file, wait))
1119
			mask |= EPOLLOUT | EPOLLWRNORM;
Linus Torvalds's avatar
Linus Torvalds committed
1120 1121 1122 1123 1124 1125 1126 1127
	}

	return mask;
}


/*-----------------------------------------------------*/

1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
static int snd_seq_ioctl_pversion(struct snd_seq_client *client, void *arg)
{
	int *pversion = arg;

	*pversion = SNDRV_SEQ_VERSION;
	return 0;
}

static int snd_seq_ioctl_client_id(struct snd_seq_client *client, void *arg)
{
	int *client_id = arg;

	*client_id = client->number;
	return 0;
}
Linus Torvalds's avatar
Linus Torvalds committed
1143 1144

/* SYSTEM_INFO ioctl() */
1145
static int snd_seq_ioctl_system_info(struct snd_seq_client *client, void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1146
{
1147
	struct snd_seq_system_info *info = arg;
Linus Torvalds's avatar
Linus Torvalds committed
1148

1149
	memset(info, 0, sizeof(*info));
Linus Torvalds's avatar
Linus Torvalds committed
1150
	/* fill the info fields */
1151 1152 1153 1154 1155 1156 1157
	info->queues = SNDRV_SEQ_MAX_QUEUES;
	info->clients = SNDRV_SEQ_MAX_CLIENTS;
	info->ports = SNDRV_SEQ_MAX_PORTS;
	info->channels = 256;	/* fixed limit */
	info->cur_clients = client_usage.cur;
	info->cur_queues = snd_seq_queue_get_cur_queues();

Linus Torvalds's avatar
Linus Torvalds committed
1158 1159 1160 1161 1162
	return 0;
}


/* RUNNING_MODE ioctl() */
1163
static int snd_seq_ioctl_running_mode(struct snd_seq_client *client, void  *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1164
{
1165
	struct snd_seq_running_info *info = arg;
1166
	struct snd_seq_client *cptr;
Linus Torvalds's avatar
Linus Torvalds committed
1167 1168 1169
	int err = 0;

	/* requested client number */
1170
	cptr = snd_seq_client_use_ptr(info->client);
Linus Torvalds's avatar
Linus Torvalds committed
1171 1172 1173 1174
	if (cptr == NULL)
		return -ENOENT;		/* don't change !!! */

#ifdef SNDRV_BIG_ENDIAN
1175
	if (!info->big_endian) {
Linus Torvalds's avatar
Linus Torvalds committed
1176 1177 1178 1179
		err = -EINVAL;
		goto __err;
	}
#else
1180
	if (info->big_endian) {
Linus Torvalds's avatar
Linus Torvalds committed
1181 1182 1183 1184 1185
		err = -EINVAL;
		goto __err;
	}

#endif
1186
	if (info->cpu_mode > sizeof(long)) {
Linus Torvalds's avatar
Linus Torvalds committed
1187 1188 1189
		err = -EINVAL;
		goto __err;
	}
1190
	cptr->convert32 = (info->cpu_mode < sizeof(long));
Linus Torvalds's avatar
Linus Torvalds committed
1191 1192 1193 1194 1195 1196
 __err:
	snd_seq_client_unlock(cptr);
	return err;
}

/* CLIENT_INFO ioctl() */
1197 1198
static void get_client_info(struct snd_seq_client *cptr,
			    struct snd_seq_client_info *info)
Linus Torvalds's avatar
Linus Torvalds committed
1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
{
	info->client = cptr->number;

	/* fill the info fields */
	info->type = cptr->type;
	strcpy(info->name, cptr->name);
	info->filter = cptr->filter;
	info->event_lost = cptr->event_lost;
	memcpy(info->event_filter, cptr->event_filter, 32);
	info->num_ports = cptr->num_ports;
1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219

	if (cptr->type == USER_CLIENT)
		info->pid = pid_vnr(cptr->data.user.owner);
	else
		info->pid = -1;

	if (cptr->type == KERNEL_CLIENT)
		info->card = cptr->data.kernel.card ? cptr->data.kernel.card->number : -1;
	else
		info->card = -1;

Linus Torvalds's avatar
Linus Torvalds committed
1220 1221 1222
	memset(info->reserved, 0, sizeof(info->reserved));
}

1223
static int snd_seq_ioctl_get_client_info(struct snd_seq_client *client,
1224
					 void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1225
{
1226
	struct snd_seq_client_info *client_info = arg;
1227
	struct snd_seq_client *cptr;
Linus Torvalds's avatar
Linus Torvalds committed
1228 1229

	/* requested client number */
1230
	cptr = snd_seq_client_use_ptr(client_info->client);
Linus Torvalds's avatar
Linus Torvalds committed
1231 1232 1233
	if (cptr == NULL)
		return -ENOENT;		/* don't change !!! */

1234
	get_client_info(cptr, client_info);
Linus Torvalds's avatar
Linus Torvalds committed
1235 1236 1237 1238 1239 1240 1241
	snd_seq_client_unlock(cptr);

	return 0;
}


/* CLIENT_INFO ioctl() */
1242
static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client,
1243
					 void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1244
{
1245
	struct snd_seq_client_info *client_info = arg;
Linus Torvalds's avatar
Linus Torvalds committed
1246 1247

	/* it is not allowed to set the info fields for an another client */
1248
	if (client->number != client_info->client)
Linus Torvalds's avatar
Linus Torvalds committed
1249 1250
		return -EPERM;
	/* also client type must be set now */
1251
	if (client->type != client_info->type)
Linus Torvalds's avatar
Linus Torvalds committed
1252 1253 1254
		return -EINVAL;

	/* fill the info fields */
1255 1256
	if (client_info->name[0])
		strlcpy(client->name, client_info->name, sizeof(client->name));
Linus Torvalds's avatar
Linus Torvalds committed
1257

1258 1259 1260
	client->filter = client_info->filter;
	client->event_lost = client_info->event_lost;
	memcpy(client->event_filter, client_info->event_filter, 32);
Linus Torvalds's avatar
Linus Torvalds committed
1261 1262 1263 1264 1265 1266 1267 1268

	return 0;
}


/* 
 * CREATE PORT ioctl() 
 */
1269
static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1270
{
1271
	struct snd_seq_port_info *info = arg;
1272 1273
	struct snd_seq_client_port *port;
	struct snd_seq_port_callback *callback;
1274
	int port_idx;
Linus Torvalds's avatar
Linus Torvalds committed
1275 1276

	/* it is not allowed to create the port for an another client */
1277
	if (info->addr.client != client->number)
Linus Torvalds's avatar
Linus Torvalds committed
1278 1279
		return -EPERM;

1280
	port = snd_seq_create_port(client, (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info->addr.port : -1);
Linus Torvalds's avatar
Linus Torvalds committed
1281 1282 1283
	if (port == NULL)
		return -ENOMEM;

1284
	if (client->type == USER_CLIENT && info->kernel) {
1285 1286 1287
		port_idx = port->addr.port;
		snd_seq_port_unlock(port);
		snd_seq_delete_port(client, port_idx);
Linus Torvalds's avatar
Linus Torvalds committed
1288 1289 1290
		return -EINVAL;
	}
	if (client->type == KERNEL_CLIENT) {
1291
		if ((callback = info->kernel) != NULL) {
Linus Torvalds's avatar
Linus Torvalds committed
1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303
			if (callback->owner)
				port->owner = callback->owner;
			port->private_data = callback->private_data;
			port->private_free = callback->private_free;
			port->event_input = callback->event_input;
			port->c_src.open = callback->subscribe;
			port->c_src.close = callback->unsubscribe;
			port->c_dest.open = callback->use;
			port->c_dest.close = callback->unuse;
		}
	}

1304
	info->addr = port->addr;
Linus Torvalds's avatar
Linus Torvalds committed
1305

1306
	snd_seq_set_port_info(port, info);
Linus Torvalds's avatar
Linus Torvalds committed
1307
	snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port);
1308
	snd_seq_port_unlock(port);
Linus Torvalds's avatar
Linus Torvalds committed
1309 1310 1311 1312 1313 1314 1315

	return 0;
}

/* 
 * DELETE PORT ioctl() 
 */
1316
static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1317
{
1318
	struct snd_seq_port_info *info = arg;
Linus Torvalds's avatar
Linus Torvalds committed
1319 1320 1321
	int err;

	/* it is not allowed to remove the port for an another client */
1322
	if (info->addr.client != client->number)
Linus Torvalds's avatar
Linus Torvalds committed
1323 1324
		return -EPERM;

1325
	err = snd_seq_delete_port(client, info->addr.port);
Linus Torvalds's avatar
Linus Torvalds committed
1326
	if (err >= 0)
1327
		snd_seq_system_client_ev_port_exit(client->number, info->addr.port);
Linus Torvalds's avatar
Linus Torvalds committed
1328 1329 1330 1331 1332 1333 1334
	return err;
}


/* 
 * GET_PORT_INFO ioctl() (on any client) 
 */
1335
static int snd_seq_ioctl_get_port_info(struct snd_seq_client *client, void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1336
{
1337
	struct snd_seq_port_info *info = arg;
1338 1339
	struct snd_seq_client *cptr;
	struct snd_seq_client_port *port;
Linus Torvalds's avatar
Linus Torvalds committed
1340

1341
	cptr = snd_seq_client_use_ptr(info->addr.client);
Linus Torvalds's avatar
Linus Torvalds committed
1342 1343 1344
	if (cptr == NULL)
		return -ENXIO;

1345
	port = snd_seq_port_use_ptr(cptr, info->addr.port);
Linus Torvalds's avatar
Linus Torvalds committed
1346 1347 1348 1349 1350 1351
	if (port == NULL) {
		snd_seq_client_unlock(cptr);
		return -ENOENT;			/* don't change */
	}

	/* get port info */
1352
	snd_seq_get_port_info(port, info);
Linus Torvalds's avatar
Linus Torvalds committed
1353 1354 1355 1356 1357 1358 1359 1360 1361 1362
	snd_seq_port_unlock(port);
	snd_seq_client_unlock(cptr);

	return 0;
}


/* 
 * SET_PORT_INFO ioctl() (only ports on this/own client) 
 */
1363
static int snd_seq_ioctl_set_port_info(struct snd_seq_client *client, void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1364
{
1365
	struct snd_seq_port_info *info = arg;
1366
	struct snd_seq_client_port *port;
Linus Torvalds's avatar
Linus Torvalds committed
1367

1368
	if (info->addr.client != client->number) /* only set our own ports ! */
Linus Torvalds's avatar
Linus Torvalds committed
1369
		return -EPERM;
1370
	port = snd_seq_port_use_ptr(client, info->addr.port);
Linus Torvalds's avatar
Linus Torvalds committed
1371
	if (port) {
1372
		snd_seq_set_port_info(port, info);
Linus Torvalds's avatar
Linus Torvalds committed
1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384
		snd_seq_port_unlock(port);
	}
	return 0;
}


/*
 * port subscription (connection)
 */
#define PERM_RD		(SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ)
#define PERM_WR		(SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE)

1385 1386 1387 1388
static int check_subscription_permission(struct snd_seq_client *client,
					 struct snd_seq_client_port *sport,
					 struct snd_seq_client_port *dport,
					 struct snd_seq_port_subscribe *subs)
Linus Torvalds's avatar
Linus Torvalds committed
1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419
{
	if (client->number != subs->sender.client &&
	    client->number != subs->dest.client) {
		/* connection by third client - check export permission */
		if (check_port_perm(sport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
			return -EPERM;
		if (check_port_perm(dport, SNDRV_SEQ_PORT_CAP_NO_EXPORT))
			return -EPERM;
	}

	/* check read permission */
	/* if sender or receiver is the subscribing client itself,
	 * no permission check is necessary
	 */
	if (client->number != subs->sender.client) {
		if (! check_port_perm(sport, PERM_RD))
			return -EPERM;
	}
	/* check write permission */
	if (client->number != subs->dest.client) {
		if (! check_port_perm(dport, PERM_WR))
			return -EPERM;
	}
	return 0;
}

/*
 * send an subscription notify event to user client:
 * client must be user client.
 */
int snd_seq_client_notify_subscription(int client, int port,
1420 1421
				       struct snd_seq_port_subscribe *info,
				       int evtype)
Linus Torvalds's avatar
Linus Torvalds committed
1422
{
1423
	struct snd_seq_event event;
Linus Torvalds's avatar
Linus Torvalds committed
1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436

	memset(&event, 0, sizeof(event));
	event.type = evtype;
	event.data.connect.dest = info->dest;
	event.data.connect.sender = info->sender;

	return snd_seq_system_notify(client, port, &event);  /* non-atomic */
}


/* 
 * add to port's subscription list IOCTL interface 
 */
1437
static int snd_seq_ioctl_subscribe_port(struct snd_seq_client *client,
1438
					void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1439
{
1440
	struct snd_seq_port_subscribe *subs = arg;
Linus Torvalds's avatar
Linus Torvalds committed
1441
	int result = -EINVAL;
1442 1443
	struct snd_seq_client *receiver = NULL, *sender = NULL;
	struct snd_seq_client_port *sport = NULL, *dport = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
1444

1445
	if ((receiver = snd_seq_client_use_ptr(subs->dest.client)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1446
		goto __end;
1447
	if ((sender = snd_seq_client_use_ptr(subs->sender.client)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1448
		goto __end;
1449
	if ((sport = snd_seq_port_use_ptr(sender, subs->sender.port)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1450
		goto __end;
1451
	if ((dport = snd_seq_port_use_ptr(receiver, subs->dest.port)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1452 1453
		goto __end;

1454
	result = check_subscription_permission(client, sport, dport, subs);
Linus Torvalds's avatar
Linus Torvalds committed
1455 1456 1457 1458
	if (result < 0)
		goto __end;

	/* connect them */
1459
	result = snd_seq_port_connect(client, sender, sport, receiver, dport, subs);
Linus Torvalds's avatar
Linus Torvalds committed
1460 1461
	if (! result) /* broadcast announce */
		snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
1462
						   subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED);
Linus Torvalds's avatar
Linus Torvalds committed
1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478
      __end:
      	if (sport)
		snd_seq_port_unlock(sport);
	if (dport)
		snd_seq_port_unlock(dport);
	if (sender)
		snd_seq_client_unlock(sender);
	if (receiver)
		snd_seq_client_unlock(receiver);
	return result;
}


/* 
 * remove from port's subscription list 
 */
1479
static int snd_seq_ioctl_unsubscribe_port(struct snd_seq_client *client,
1480
					  void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1481
{
1482
	struct snd_seq_port_subscribe *subs = arg;
Linus Torvalds's avatar
Linus Torvalds committed
1483
	int result = -ENXIO;
1484 1485
	struct snd_seq_client *receiver = NULL, *sender = NULL;
	struct snd_seq_client_port *sport = NULL, *dport = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
1486

1487
	if ((receiver = snd_seq_client_use_ptr(subs->dest.client)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1488
		goto __end;
1489
	if ((sender = snd_seq_client_use_ptr(subs->sender.client)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1490
		goto __end;
1491
	if ((sport = snd_seq_port_use_ptr(sender, subs->sender.port)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1492
		goto __end;
1493
	if ((dport = snd_seq_port_use_ptr(receiver, subs->dest.port)) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
1494 1495
		goto __end;

1496
	result = check_subscription_permission(client, sport, dport, subs);
Linus Torvalds's avatar
Linus Torvalds committed
1497 1498 1499
	if (result < 0)
		goto __end;

1500
	result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, subs);
Linus Torvalds's avatar
Linus Torvalds committed
1501 1502
	if (! result) /* broadcast announce */
		snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0,
1503
						   subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED);
Linus Torvalds's avatar
Linus Torvalds committed
1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517
      __end:
      	if (sport)
		snd_seq_port_unlock(sport);
	if (dport)
		snd_seq_port_unlock(dport);
	if (sender)
		snd_seq_client_unlock(sender);
	if (receiver)
		snd_seq_client_unlock(receiver);
	return result;
}


/* CREATE_QUEUE ioctl() */
1518
static int snd_seq_ioctl_create_queue(struct snd_seq_client *client, void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1519
{
1520
	struct snd_seq_queue_info *info = arg;
1521
	struct snd_seq_queue *q;
Linus Torvalds's avatar
Linus Torvalds committed
1522

1523 1524 1525
	q = snd_seq_queue_alloc(client->number, info->locked, info->flags);
	if (IS_ERR(q))
		return PTR_ERR(q);
Linus Torvalds's avatar
Linus Torvalds committed
1526

1527 1528 1529
	info->queue = q->queue;
	info->locked = q->locked;
	info->owner = q->owner;
Linus Torvalds's avatar
Linus Torvalds committed
1530 1531

	/* set queue name */
1532 1533 1534
	if (!info->name[0])
		snprintf(info->name, sizeof(info->name), "Queue-%d", q->queue);
	strlcpy(q->name, info->name, sizeof(q->name));
1535
	snd_use_lock_free(&q->use_lock);
Linus Torvalds's avatar
Linus Torvalds committed
1536 1537 1538 1539 1540

	return 0;
}

/* DELETE_QUEUE ioctl() */
1541
static int snd_seq_ioctl_delete_queue(struct snd_seq_client *client, void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1542
{
1543
	struct snd_seq_queue_info *info = arg;
Linus Torvalds's avatar
Linus Torvalds committed
1544

1545
	return snd_seq_queue_delete(client->number, info->queue);
Linus Torvalds's avatar
Linus Torvalds committed
1546 1547 1548
}

/* GET_QUEUE_INFO ioctl() */
1549
static int snd_seq_ioctl_get_queue_info(struct snd_seq_client *client,
1550
					void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1551
{
1552
	struct snd_seq_queue_info *info = arg;
1553
	struct snd_seq_queue *q;
Linus Torvalds's avatar
Linus Torvalds committed
1554

1555
	q = queueptr(info->queue);
Linus Torvalds's avatar
Linus Torvalds committed
1556 1557 1558
	if (q == NULL)
		return -EINVAL;

1559 1560 1561 1562 1563
	memset(info, 0, sizeof(*info));
	info->queue = q->queue;
	info->owner = q->owner;
	info->locked = q->locked;
	strlcpy(info->name, q->name, sizeof(info->name));
Linus Torvalds's avatar
Linus Torvalds committed
1564 1565 1566 1567 1568 1569
	queuefree(q);

	return 0;
}

/* SET_QUEUE_INFO ioctl() */
1570
static int snd_seq_ioctl_set_queue_info(struct snd_seq_client *client,
1571
					void *arg)
Linus Torvalds's avatar
Linus Torvalds committed
1572
{
1573
	struct snd_seq_queue_info *info = arg;
1574
	struct snd_seq_queue *q;
Linus Torvalds's avatar
Linus Torvalds committed
1575

1576
	if (info->owner != client->number)
Linus Torvalds's avatar
Linus Torvalds committed
1577 1578 1579
		return -EINVAL;

	/* change owner/locked permission */
1580 1581
	if (snd_seq_queue_check_access(info->queue, client->number)) {
		if (snd_seq_queue_set_owner(info->queue, client->number, info->locked) < 0)
Linus Torvalds's avatar
Linus Torvalds committed
1582
			return -EPERM;
1583 1584
		if (info->locked)
			snd_seq_queue_use(info->queue, client->number, 1);
Linus Torvalds's avatar
Linus Torvalds committed
1585 1586 1587 1588
	} else {
		return -EPERM;
	}	

1589
	q = queueptr(info->queue);
Linus Torvalds's avatar
Linus Torvalds committed
1590 1591 1592 1593 1594 1595
	if (! q)
		return -EINVAL;
	if (q->owner != client->number) {
		queuefree(q);
		return -EPERM;
	}
Takashi Sakamoto's avatar