linux/sound/synth/emux/emux_oss.c
Takashi Iwai 1c94e65c66 ALSA: emux: Fix mutex deadlock in OSS emulation
The OSS emulation in synth-emux helper has a potential AB/BA deadlock
at the simultaneous closing and opening:

  close ->
    snd_seq_release() ->
      sne_seq_free_client() ->
        snd_seq_delete_all_ports(): takes client->ports_mutex ->
	  port_delete() ->
	    snd_emux_unuse(): takes emux->register_mutex

  open ->
    snd_seq_oss_open() ->
      snd_emux_open_seq_oss(): takes emux->register_mutex ->
        snd_seq_event_port_attach() ->
	  snd_seq_create_port(): takes client->ports_mutex

This patch addresses the deadlock by reducing the rance taking
emux->register_mutex in snd_emux_open_seq_oss().  The lock is needed
for the refcount handling, so move it locally.  The calls in
emux_seq.c are already with the mutex, thus they are replaced with the
version without mutex lock/unlock.

Cc: <stable@vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-04-28 17:45:45 +02:00

509 lines
12 KiB
C

/*
* Interface for OSS sequencer emulation
*
* Copyright (C) 1999 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
*
* Changes
* 19990227 Steve Ratcliffe Made separate file and merged in latest
* midi emulation.
*/
#ifdef CONFIG_SND_SEQUENCER_OSS
#include <linux/export.h>
#include <linux/uaccess.h>
#include <sound/core.h>
#include "emux_voice.h"
#include <sound/asoundef.h>
static int snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure);
static int snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg);
static int snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd,
unsigned long ioarg);
static int snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
const char __user *buf, int offs, int count);
static int snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg);
static int snd_emux_event_oss_input(struct snd_seq_event *ev, int direct,
void *private, int atomic, int hop);
static void reset_port_mode(struct snd_emux_port *port, int midi_mode);
static void emuspec_control(struct snd_emux *emu, struct snd_emux_port *port,
int cmd, unsigned char *event, int atomic, int hop);
static void gusspec_control(struct snd_emux *emu, struct snd_emux_port *port,
int cmd, unsigned char *event, int atomic, int hop);
static void fake_event(struct snd_emux *emu, struct snd_emux_port *port,
int ch, int param, int val, int atomic, int hop);
/* operators */
static struct snd_seq_oss_callback oss_callback = {
.owner = THIS_MODULE,
.open = snd_emux_open_seq_oss,
.close = snd_emux_close_seq_oss,
.ioctl = snd_emux_ioctl_seq_oss,
.load_patch = snd_emux_load_patch_seq_oss,
.reset = snd_emux_reset_seq_oss,
};
/*
* register OSS synth
*/
void
snd_emux_init_seq_oss(struct snd_emux *emu)
{
struct snd_seq_oss_reg *arg;
struct snd_seq_device *dev;
if (snd_seq_device_new(emu->card, 0, SNDRV_SEQ_DEV_ID_OSS,
sizeof(struct snd_seq_oss_reg), &dev) < 0)
return;
emu->oss_synth = dev;
strcpy(dev->name, emu->name);
arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
arg->type = SYNTH_TYPE_SAMPLE;
arg->subtype = SAMPLE_TYPE_AWE32;
arg->nvoices = emu->max_voices;
arg->oper = oss_callback;
arg->private_data = emu;
/* register to OSS synth table */
snd_device_register(emu->card, dev);
}
/*
* unregister
*/
void
snd_emux_detach_seq_oss(struct snd_emux *emu)
{
if (emu->oss_synth) {
snd_device_free(emu->card, emu->oss_synth);
emu->oss_synth = NULL;
}
}
/* use port number as a unique soundfont client number */
#define SF_CLIENT_NO(p) ((p) + 0x1000)
/*
* open port for OSS sequencer
*/
static int
snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
{
struct snd_emux *emu;
struct snd_emux_port *p;
struct snd_seq_port_callback callback;
char tmpname[64];
emu = closure;
if (snd_BUG_ON(!arg || !emu))
return -ENXIO;
if (!snd_emux_inc_count(emu))
return -EFAULT;
memset(&callback, 0, sizeof(callback));
callback.owner = THIS_MODULE;
callback.event_input = snd_emux_event_oss_input;
sprintf(tmpname, "%s OSS Port", emu->name);
p = snd_emux_create_port(emu, tmpname, 32,
1, &callback);
if (p == NULL) {
snd_printk(KERN_ERR "can't create port\n");
snd_emux_dec_count(emu);
return -ENOMEM;
}
/* fill the argument data */
arg->private_data = p;
arg->addr.client = p->chset.client;
arg->addr.port = p->chset.port;
p->oss_arg = arg;
reset_port_mode(p, arg->seq_mode);
snd_emux_reset_port(p);
return 0;
}
#define DEFAULT_DRUM_FLAGS ((1<<9) | (1<<25))
/*
* reset port mode
*/
static void
reset_port_mode(struct snd_emux_port *port, int midi_mode)
{
if (midi_mode) {
port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI;
port->drum_flags = DEFAULT_DRUM_FLAGS;
port->volume_atten = 0;
port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS;
} else {
port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH;
port->drum_flags = 0;
port->volume_atten = 32;
port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
}
}
/*
* close port
*/
static int
snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg)
{
struct snd_emux *emu;
struct snd_emux_port *p;
if (snd_BUG_ON(!arg))
return -ENXIO;
p = arg->private_data;
if (snd_BUG_ON(!p))
return -ENXIO;
emu = p->emu;
if (snd_BUG_ON(!emu))
return -ENXIO;
snd_emux_sounds_off_all(p);
snd_soundfont_close_check(emu->sflist, SF_CLIENT_NO(p->chset.port));
snd_seq_event_port_detach(p->chset.client, p->chset.port);
snd_emux_dec_count(emu);
return 0;
}
/*
* load patch
*/
static int
snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
const char __user *buf, int offs, int count)
{
struct snd_emux *emu;
struct snd_emux_port *p;
int rc;
if (snd_BUG_ON(!arg))
return -ENXIO;
p = arg->private_data;
if (snd_BUG_ON(!p))
return -ENXIO;
emu = p->emu;
if (snd_BUG_ON(!emu))
return -ENXIO;
if (format == GUS_PATCH)
rc = snd_soundfont_load_guspatch(emu->sflist, buf, count,
SF_CLIENT_NO(p->chset.port));
else if (format == SNDRV_OSS_SOUNDFONT_PATCH) {
struct soundfont_patch_info patch;
if (count < (int)sizeof(patch))
rc = -EINVAL;
if (copy_from_user(&patch, buf, sizeof(patch)))
rc = -EFAULT;
if (patch.type >= SNDRV_SFNT_LOAD_INFO &&
patch.type <= SNDRV_SFNT_PROBE_DATA)
rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port));
else {
if (emu->ops.load_fx)
rc = emu->ops.load_fx(emu, patch.type, patch.optarg, buf, count);
else
rc = -EINVAL;
}
} else
rc = 0;
return rc;
}
/*
* ioctl
*/
static int
snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg)
{
struct snd_emux_port *p;
struct snd_emux *emu;
if (snd_BUG_ON(!arg))
return -ENXIO;
p = arg->private_data;
if (snd_BUG_ON(!p))
return -ENXIO;
emu = p->emu;
if (snd_BUG_ON(!emu))
return -ENXIO;
switch (cmd) {
case SNDCTL_SEQ_RESETSAMPLES:
snd_soundfont_remove_samples(emu->sflist);
return 0;
case SNDCTL_SYNTH_MEMAVL:
if (emu->memhdr)
return snd_util_mem_avail(emu->memhdr);
return 0;
}
return 0;
}
/*
* reset device
*/
static int
snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg)
{
struct snd_emux_port *p;
if (snd_BUG_ON(!arg))
return -ENXIO;
p = arg->private_data;
if (snd_BUG_ON(!p))
return -ENXIO;
snd_emux_reset_port(p);
return 0;
}
/*
* receive raw events: only SEQ_PRIVATE is accepted.
*/
static int
snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, void *private_data,
int atomic, int hop)
{
struct snd_emux *emu;
struct snd_emux_port *p;
unsigned char cmd, *data;
p = private_data;
if (snd_BUG_ON(!p))
return -EINVAL;
emu = p->emu;
if (snd_BUG_ON(!emu))
return -EINVAL;
if (ev->type != SNDRV_SEQ_EVENT_OSS)
return snd_emux_event_input(ev, direct, private_data, atomic, hop);
data = ev->data.raw8.d;
/* only SEQ_PRIVATE is accepted */
if (data[0] != 0xfe)
return 0;
cmd = data[2] & _EMUX_OSS_MODE_VALUE_MASK;
if (data[2] & _EMUX_OSS_MODE_FLAG)
emuspec_control(emu, p, cmd, data, atomic, hop);
else
gusspec_control(emu, p, cmd, data, atomic, hop);
return 0;
}
/*
* OSS/AWE driver specific h/w controls
*/
static void
emuspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd,
unsigned char *event, int atomic, int hop)
{
int voice;
unsigned short p1;
short p2;
int i;
struct snd_midi_channel *chan;
voice = event[3];
if (voice < 0 || voice >= port->chset.max_channels)
chan = NULL;
else
chan = &port->chset.channels[voice];
p1 = *(unsigned short *) &event[4];
p2 = *(short *) &event[6];
switch (cmd) {
#if 0 /* don't do this atomically */
case _EMUX_OSS_REMOVE_LAST_SAMPLES:
snd_soundfont_remove_unlocked(emu->sflist);
break;
#endif
case _EMUX_OSS_SEND_EFFECT:
if (chan)
snd_emux_send_effect_oss(port, chan, p1, p2);
break;
case _EMUX_OSS_TERMINATE_ALL:
snd_emux_terminate_all(emu);
break;
case _EMUX_OSS_TERMINATE_CHANNEL:
/*snd_emux_mute_channel(emu, chan);*/
break;
case _EMUX_OSS_RESET_CHANNEL:
/*snd_emux_channel_init(chset, chan);*/
break;
case _EMUX_OSS_RELEASE_ALL:
fake_event(emu, port, voice, MIDI_CTL_ALL_NOTES_OFF, 0, atomic, hop);
break;
case _EMUX_OSS_NOTEOFF_ALL:
fake_event(emu, port, voice, MIDI_CTL_ALL_SOUNDS_OFF, 0, atomic, hop);
break;
case _EMUX_OSS_INITIAL_VOLUME:
if (p2) {
port->volume_atten = (short)p1;
snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME);
}
break;
case _EMUX_OSS_CHN_PRESSURE:
if (chan) {
chan->midi_pressure = p1;
snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_FMMOD|SNDRV_EMUX_UPDATE_FM2FRQ2);
}
break;
case _EMUX_OSS_CHANNEL_MODE:
reset_port_mode(port, p1);
snd_emux_reset_port(port);
break;
case _EMUX_OSS_DRUM_CHANNELS:
port->drum_flags = *(unsigned int*)&event[4];
for (i = 0; i < port->chset.max_channels; i++) {
chan = &port->chset.channels[i];
chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0;
}
break;
case _EMUX_OSS_MISC_MODE:
if (p1 < EMUX_MD_END)
port->ctrls[p1] = p2;
break;
case _EMUX_OSS_DEBUG_MODE:
break;
default:
if (emu->ops.oss_ioctl)
emu->ops.oss_ioctl(emu, cmd, p1, p2);
break;
}
}
/*
* GUS specific h/w controls
*/
#include <linux/ultrasound.h>
static void
gusspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd,
unsigned char *event, int atomic, int hop)
{
int voice;
unsigned short p1;
short p2;
int plong;
struct snd_midi_channel *chan;
if (port->port_mode != SNDRV_EMUX_PORT_MODE_OSS_SYNTH)
return;
if (cmd == _GUS_NUMVOICES)
return;
voice = event[3];
if (voice < 0 || voice >= port->chset.max_channels)
return;
chan = &port->chset.channels[voice];
p1 = *(unsigned short *) &event[4];
p2 = *(short *) &event[6];
plong = *(int*) &event[4];
switch (cmd) {
case _GUS_VOICESAMPLE:
chan->midi_program = p1;
return;
case _GUS_VOICEBALA:
/* 0 to 15 --> 0 to 127 */
chan->control[MIDI_CTL_MSB_PAN] = (int)p1 << 3;
snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN);
return;
case _GUS_VOICEVOL:
case _GUS_VOICEVOL2:
/* not supported yet */
return;
case _GUS_RAMPRANGE:
case _GUS_RAMPRATE:
case _GUS_RAMPMODE:
case _GUS_RAMPON:
case _GUS_RAMPOFF:
/* volume ramping not supported */
return;
case _GUS_VOLUME_SCALE:
return;
case _GUS_VOICE_POS:
#ifdef SNDRV_EMUX_USE_RAW_EFFECT
snd_emux_send_effect(port, chan, EMUX_FX_SAMPLE_START,
(short)(plong & 0x7fff),
EMUX_FX_FLAG_SET);
snd_emux_send_effect(port, chan, EMUX_FX_COARSE_SAMPLE_START,
(plong >> 15) & 0xffff,
EMUX_FX_FLAG_SET);
#endif
return;
}
}
/*
* send an event to midi emulation
*/
static void
fake_event(struct snd_emux *emu, struct snd_emux_port *port, int ch, int param, int val, int atomic, int hop)
{
struct snd_seq_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
ev.data.control.channel = ch;
ev.data.control.param = param;
ev.data.control.value = val;
snd_emux_event_input(&ev, 0, port, atomic, hop);
}
#endif /* CONFIG_SND_SEQUENCER_OSS */