2732 lines · 71.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *  ALSA sequencer Client Manager *  Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl> *                             Jaroslav Kysela <perex@perex.cz> *                             Takashi Iwai <tiwai@suse.de> */ #include <linux/init.h>#include <linux/export.h>#include <linux/slab.h>#include <sound/core.h>#include <sound/minors.h>#include <linux/kmod.h> #include <sound/seq_kernel.h>#include <sound/ump.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 "seq_ump_convert.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 *  */ /* * 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 #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);static DEFINE_MUTEX(register_mutex); /* * client table */static char clienttablock[SNDRV_SEQ_MAX_CLIENTS];static struct snd_seq_client __rcu *clienttab[SNDRV_SEQ_MAX_CLIENTS];static struct snd_seq_usage client_usage; /* * prototypes */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 atomic, int hop); #if IS_ENABLED(CONFIG_SND_SEQ_UMP)static void free_ump_info(struct snd_seq_client *client);#endif /* */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;        }} static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client){	return snd_seq_total_cells(client->pool) > 0;} /* return pointer to client structure for specified id; call under RCU read-lock */static struct snd_seq_client *__clientptr(int clientid){	if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {		pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n",			   clientid);		return NULL;	}	return rcu_dereference_check(clienttab[clientid],				    lockdep_is_held(&clients_lock));} /* return pointer to client structure for specified id */static struct snd_seq_client *clientptr(int clientid){	guard(rcu)();	return __clientptr(clientid);} static struct snd_seq_client *client_use_ptr(int clientid, bool load_module){	struct snd_seq_client *client; 	if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) {		pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n",			   clientid);		return NULL;	}	scoped_guard(rcu) {		client = __clientptr(clientid);		if (client)			return snd_seq_client_ref(client);		if (clienttablock[clientid])			return NULL;	}#ifdef CONFIG_MODULES	if (load_module) {		static DECLARE_BITMAP(client_requested, SNDRV_SEQ_GLOBAL_CLIENTS);		static DECLARE_BITMAP(card_requested, SNDRV_CARDS); 		if (clientid < SNDRV_SEQ_GLOBAL_CLIENTS) {			int idx;						if (!test_and_set_bit(clientid, client_requested)) {				for (idx = 0; idx < 15; idx++) {					if (seq_client_load[idx] < 0)						break;					if (seq_client_load[idx] == clientid) {						request_module("snd-seq-client-%i",							       clientid);						break;					}				}			}		} else if (clientid < SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) {			int card = (clientid - SNDRV_SEQ_GLOBAL_CLIENTS) /				SNDRV_SEQ_CLIENTS_PER_CARD;			if (card < snd_ecards_limit) {				if (!test_and_set_bit(card, card_requested))					snd_request_card(card);				snd_seq_device_load_drivers();			}		}		scoped_guard(rcu) {			client = __clientptr(clientid);			if (client)				return snd_seq_client_ref(client);		}	}#endif	return NULL;} /* get snd_seq_client object for the given id quickly */struct snd_seq_client *snd_seq_client_use_ptr(int clientid){	return client_use_ptr(clientid, false);} /* get snd_seq_client object for the given id; * if not found, retry after loading the modules */static struct snd_seq_client *client_load_and_use_ptr(int clientid){	return client_use_ptr(clientid, IS_ENABLED(CONFIG_MODULES));} static void usage_alloc(struct snd_seq_usage *res, int num){	res->cur += num;	if (res->cur > res->peak)		res->peak = res->cur;} static void usage_free(struct snd_seq_usage *res, int num){	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;}  static struct snd_seq_client *seq_create_client1(int client_index, int poolsize){	int c;	struct snd_seq_client *client; 	/* init client data */	client = kzalloc_obj(*client);	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);	mutex_init(&client->ports_mutex);	INIT_LIST_HEAD(&client->ports_list_head);	mutex_init(&client->ioctl_mutex);	client->ump_endpoint_port = -1; 	/* find free slot in the client table */	scoped_guard(spinlock_irq, &clients_lock) {		if (client_index < 0) {			for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN;			     c < SNDRV_SEQ_MAX_CLIENTS;			     c++) {				if (rcu_access_pointer(clienttab[c]) || clienttablock[c])					continue;				client->number = c;				rcu_assign_pointer(clienttab[c], client);				return client;			}		} else {			if (rcu_access_pointer(clienttab[client_index]) == NULL &&			    !clienttablock[client_index]) {				client->number = client_index;				rcu_assign_pointer(clienttab[client_index], client);				return client;			}		}	} 	snd_seq_pool_delete(&client->pool);	kfree(client);	return NULL;	/* no free slot found or busy, return failure code */}  static int seq_free_client1(struct snd_seq_client *client){	if (!client)		return 0;	scoped_guard(spinlock_irq, &clients_lock) {		clienttablock[client->number] = 1;		rcu_assign_pointer(clienttab[client->number], NULL);	}	snd_seq_delete_all_ports(client);	snd_seq_queue_client_leave(client->number);	/* the client has been unpublished from the table; wait for a grace	 * period so that lockless readers (snd_seq_client_use_ptr()) that	 * observed the old pointer can no longer take a new use_lock	 * reference, then drain the outstanding references before freeing	 */	synchronize_rcu();	snd_use_lock_sync(&client->use_lock);	if (client->pool)		snd_seq_pool_delete(&client->pool);	scoped_guard(spinlock_irq, &clients_lock) {		clienttablock[client->number] = 0;	}	return 0;}  static void seq_free_client(struct snd_seq_client * client){	scoped_guard(mutex, &register_mutex) {		switch (client->type) {		case NO_CLIENT:			pr_warn("ALSA: seq: Trying to free unused client %d\n",				client->number);			break;		case USER_CLIENT:		case KERNEL_CLIENT:			seq_free_client1(client);			usage_free(&client_usage, 1);			break; 		default:			pr_err("ALSA: seq: Trying to free client %d with undefined type = %d\n",			       client->number, client->type);		}	} 	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 */	struct snd_seq_client *client;	struct snd_seq_user_client *user; 	stream_open(inode, file); 	scoped_guard(mutex, &register_mutex) {		client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS);		if (!client)			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);				return -ENOMEM;			}		} 		usage_alloc(&client_usage, 1);		client->type = USER_CLIENT;	} 	c = client->number;	file->private_data = client; 	/* fill client data */	user->file = file;	sprintf(client->name, "Client-%d", c);	client->data.user.owner = get_pid(task_pid(current)); 	/* 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){	struct snd_seq_client *client = file->private_data; 	if (client) {		seq_free_client(client);		if (client->data.user.fifo)			snd_seq_fifo_delete(&client->data.user.fifo);#if IS_ENABLED(CONFIG_SND_SEQ_UMP)		free_ump_info(client);#endif		put_pid(client->data.user.owner);		kfree(client);	} 	return 0;} static bool event_is_compatible(const struct snd_seq_client *client,				const struct snd_seq_event *ev){	if (snd_seq_ev_is_ump(ev) && !client->midi_version)		return false;	if (snd_seq_ev_is_ump(ev) && snd_seq_ev_is_variable(ev))		return false;	return true;} /* 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 */static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count,			    loff_t *offset){	struct snd_seq_client *client = file->private_data;	struct snd_seq_fifo *fifo;	size_t aligned_size;	int err;	long result = 0;	struct snd_seq_event_cell *cell; 	if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_INPUT))		return -ENXIO; 	if (!access_ok(buf, count))		return -EFAULT; 	/* check client structures are in place */	if (snd_BUG_ON(!client))		return -ENXIO; 	if (!client->accept_input)		return -ENXIO;	fifo = client->data.user.fifo;	if (!fifo)		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;	guard(snd_seq_fifo)(fifo); 	if (IS_ENABLED(CONFIG_SND_SEQ_UMP) && client->midi_version > 0)		aligned_size = sizeof(struct snd_seq_ump_event);	else		aligned_size = sizeof(struct snd_seq_event); 	/* while data available in queue */	while (count >= aligned_size) {		int nonblock; 		nonblock = (file->f_flags & O_NONBLOCK) || result > 0;		err = snd_seq_fifo_cell_out(fifo, &cell, nonblock);		if (err < 0)			break;		if (!event_is_compatible(client, &cell->event)) {			snd_seq_cell_free(cell);			cell = NULL;			continue;		}		if (snd_seq_ev_is_variable(&cell->event)) {			struct snd_seq_ump_event tmpev; 			memcpy(&tmpev, &cell->event, aligned_size);			tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK;			tmpev.data.ext.ptr = NULL;			if (copy_to_user(buf, &tmpev, aligned_size)) {				err = -EFAULT;				break;			}			count -= aligned_size;			buf += aligned_size;			err = snd_seq_expand_var_event(&cell->event, count,						       (char __force *)buf, 0,						       aligned_size);			if (err < 0)				break;			result += err;			count -= err;			buf += err;		} else {			if (copy_to_user(buf, &cell->event, aligned_size)) {				err = -EFAULT;				break;			}			count -= aligned_size;			buf += aligned_size;		}		snd_seq_cell_free(cell);		cell = NULL; /* to be sure */		result += aligned_size;	} 	if (err < 0) {		if (cell)			snd_seq_fifo_cell_putback(fifo, cell);		if (err == -EAGAIN && result > 0)			err = 0;	} 	return (err < 0) ? err : result;}  /* * check access permission to the port */static int check_port_perm(struct snd_seq_client_port *port, unsigned int flags){

Lines 1–500 of 2732.