📄 seq_clientmgr.c
字号:
/* * ALSA sequencer Client Manager * Copyright (c) 1998-2001 by Frank van de Pol <fvdpol@coil.demon.nl> * Jaroslav Kysela <perex@suse.cz> * 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 <sound/driver.h>#include <linux/init.h>#include <linux/smp_lock.h>#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 * */#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 DECLARE_MUTEX(register_mutex);/* * client table */static char clienttablock[SNDRV_SEQ_MAX_CLIENTS];static client_t *clienttab[SNDRV_SEQ_MAX_CLIENTS];static usage_t client_usage;/* * prototypes */static int bounce_error_event(client_t *client, snd_seq_event_t *event, int err, int atomic, int hop);static int snd_seq_deliver_single_event(client_t *client, snd_seq_event_t *event, int filter, int atomic, int hop);/* */ static inline mm_segment_t snd_enter_user(void){ mm_segment_t fs = get_fs(); set_fs(get_ds()); return fs;}static inline void snd_leave_user(mm_segment_t fs){ set_fs(fs);}/* */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(client_t *client){ return snd_seq_total_cells(client->pool) > 0;}/* return pointer to client structure for specified id */static client_t *clientptr(int clientid){ if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid); return NULL; } return clienttab[clientid];}extern int seq_client_load[];client_t *snd_seq_client_use_ptr(int clientid){ unsigned long flags; client_t *client; if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { snd_printd("Seq: oops. Trying to get pointer to client %d\n", clientid); 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);#ifdef CONFIG_KMOD if (!in_interrupt() && current->fs->root) { static char client_requested[64]; static char card_requested[SNDRV_CARDS]; if (clientid < 64) { int idx; if (! client_requested[clientid] && current->fs->root) { client_requested[clientid] = 1; for (idx = 0; idx < 64; 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 >= 64 && clientid < 128) { int card = (clientid - 64) / 8; 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;}static void usage_alloc(usage_t * res, int num){ res->cur += num; if (res->cur > res->peak) res->peak = res->cur;}static void usage_free(usage_t * 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 client_t *seq_create_client1(int client_index, int poolsize){ unsigned long flags; int c; client_t *client; /* init client data */ client = kzalloc(sizeof(*client), GFP_KERNEL); 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); init_MUTEX(&client->ports_mutex); INIT_LIST_HEAD(&client->ports_list_head); /* find free slot in the client table */ spin_lock_irqsave(&clients_lock, flags); if (client_index < 0) { for (c = 128; c < SNDRV_SEQ_MAX_CLIENTS; c++) { 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 */}static int seq_free_client1(client_t *client){ unsigned long flags; snd_assert(client != NULL, return -EINVAL); 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;}static void seq_free_client(client_t * client){ down(®ister_mutex); switch (client->type) { case NO_CLIENT: snd_printk(KERN_WARNING "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: snd_printk(KERN_ERR "Seq: Trying to free client %d with undefined type = %d\n", client->number, client->type); } up(®ister_mutex); 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 */ client_t *client; user_client_t *user; if (down_interruptible(®ister_mutex)) return -ERESTARTSYS; client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); if (client == NULL) { up(®ister_mutex); 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); up(®ister_mutex); return -ENOMEM; } } usage_alloc(&client_usage, 1); client->type = USER_CLIENT; up(®ister_mutex); c = client->number; file->private_data = client; /* fill client data */ user->file = file; sprintf(client->name, "Client-%d", c); /* 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){ client_t *client = (client_t *) file->private_data; if (client) { seq_free_client(client); if (client->data.user.fifo) snd_seq_fifo_delete(&client->data.user.fifo); 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 */static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, loff_t *offset){ client_t *client = (client_t *) file->private_data; fifo_t *fifo; int err; long result = 0; snd_seq_event_cell_t *cell; 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 */ snd_assert(client != NULL, return -ENXIO); 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 */ while (count >= sizeof(snd_seq_event_t)) { 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)) { snd_seq_event_t tmpev; tmpev = cell->event; tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; if (copy_to_user(buf, &tmpev, sizeof(snd_seq_event_t))) { err = -EFAULT; break; } count -= sizeof(snd_seq_event_t); buf += sizeof(snd_seq_event_t); err = snd_seq_expand_var_event(&cell->event, count, (char __force *)buf, 0, sizeof(snd_seq_event_t)); if (err < 0) break; result += err; count -= err; buf += err; } else { if (copy_to_user(buf, &cell->event, sizeof(snd_seq_event_t))) { err = -EFAULT; break; } count -= sizeof(snd_seq_event_t); buf += sizeof(snd_seq_event_t); } snd_seq_cell_free(cell); cell = NULL; /* to be sure */ result += sizeof(snd_seq_event_t); } 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 */static int check_port_perm(client_port_t *port, unsigned int flags){ 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. */static client_t *get_event_dest_client(snd_seq_event_t *event, int filter){ client_t *dest; 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. */static int bounce_error_event(client_t *client, snd_seq_event_t *event, int err, int atomic, int hop){ snd_seq_event_t bounce_ev; int result; if (client == NULL ||
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -