diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /sound/core | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/core')
104 files changed, 52765 insertions, 0 deletions
diff --git a/sound/core/Kconfig b/sound/core/Kconfig new file mode 100644 index 0000000000..e41818e59a --- /dev/null +++ b/sound/core/Kconfig @@ -0,0 +1,250 @@ +# SPDX-License-Identifier: GPL-2.0-only +# ALSA soundcard-configuration +config SND_TIMER + tristate + +config SND_PCM + tristate + select SND_TIMER if SND_PCM_TIMER + +config SND_PCM_ELD + bool + +config SND_PCM_IEC958 + bool + +config SND_DMAENGINE_PCM + tristate + +config SND_HWDEP + tristate + +config SND_SEQ_DEVICE + tristate + +config SND_RAWMIDI + tristate + select SND_SEQ_DEVICE if SND_SEQUENCER != n + +config SND_UMP + tristate + select SND_RAWMIDI + +config SND_UMP_LEGACY_RAWMIDI + bool "Legacy raw MIDI support for UMP streams" + depends on SND_UMP + help + This option enables the legacy raw MIDI support for UMP streams. + When this option is set, an additional rawmidi device for the + legacy MIDI 1.0 byte streams is created for each UMP Endpoint. + The device contains 16 substreams corresponding to UMP groups. + +config SND_COMPRESS_OFFLOAD + tristate + +config SND_JACK + bool + +# enable input device support in jack layer +config SND_JACK_INPUT_DEV + bool + depends on SND_JACK + default y if INPUT=y || INPUT=SND + +config SND_OSSEMUL + bool "Enable OSS Emulation" + select SOUND_OSS_CORE + help + This option enables the build of OSS emulation layer. + +config SND_MIXER_OSS + tristate "OSS Mixer API" + depends on SND_OSSEMUL + help + To enable OSS mixer API emulation (/dev/mixer*), say Y here + and read <file:Documentation/sound/designs/oss-emulation.rst>. + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-mixer-oss. + +config SND_PCM_OSS + tristate "OSS PCM (digital audio) API" + depends on SND_OSSEMUL + select SND_PCM + help + To enable OSS digital audio (PCM) emulation (/dev/dsp*), say Y + here and read <file:Documentation/sound/designs/oss-emulation.rst>. + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-pcm-oss. + +config SND_PCM_OSS_PLUGINS + bool "OSS PCM (digital audio) API - Include plugin system" + depends on SND_PCM_OSS + default y + help + If you disable this option, the ALSA's OSS PCM API will not + support conversion of channels, formats and rates. It will + behave like most of new OSS/Free drivers in 2.4/2.6 kernels. + +config SND_PCM_TIMER + bool "PCM timer interface" if EXPERT + default y + help + If you disable this option, pcm timer will be unavailable, so + those stubs that use pcm timer (e.g. dmix, dsnoop & co) may work + incorrectly. + + For some embedded devices, we may disable it to reduce memory + footprint, about 20KB on x86_64 platform. + +config SND_HRTIMER + tristate "HR-timer backend support" + depends on HIGH_RES_TIMERS + select SND_TIMER + help + Say Y here to enable HR-timer backend for ALSA timer. ALSA uses + the hrtimer as a precise timing source. The ALSA sequencer code + also can use this timing source. + + To compile this driver as a module, choose M here: the module + will be called snd-hrtimer. + +config SND_DYNAMIC_MINORS + bool "Dynamic device file minor numbers" + help + If you say Y here, the minor numbers of ALSA device files in + /dev/snd/ are allocated dynamically. This allows you to have + more than 8 sound cards, but requires a dynamic device file + system like udev. + + If you are unsure about this, say N here. + +config SND_MAX_CARDS + int "Max number of sound cards" + range 4 256 + default 32 + depends on SND_DYNAMIC_MINORS + help + Specify the max number of sound cards that can be assigned + on a single machine. + +config SND_SUPPORT_OLD_API + bool "Support old ALSA API" + default y + help + Say Y here to support the obsolete ALSA PCM API (ver.0.9.0 rc3 + or older). + +config SND_PROC_FS + bool "Sound Proc FS Support" if EXPERT + depends on PROC_FS + default y + help + Say 'N' to disable Sound proc FS, which may reduce code size about + 9KB on x86_64 platform. + If unsure say Y. + +config SND_VERBOSE_PROCFS + bool "Verbose procfs contents" + depends on SND_PROC_FS + default y + help + Say Y here to include code for verbose procfs contents (provides + useful information to developers when a problem occurs). On the + other side, it makes the ALSA subsystem larger. + +config SND_VERBOSE_PRINTK + bool "Verbose printk" + help + Say Y here to enable verbose log messages. These messages + will help to identify source file and position containing + printed messages. + + You don't need this unless you're debugging ALSA. + +config SND_CTL_FAST_LOOKUP + bool "Fast lookup of control elements" if EXPERT + default y + select XARRAY_MULTI + help + This option enables the faster lookup of control elements. + It will consume more memory because of the additional Xarray. + If you want to choose the memory footprint over the performance + inevitably, turn this off. + +config SND_DEBUG + bool "Debug" + help + Say Y here to enable ALSA debug code. + +config SND_DEBUG_VERBOSE + bool "More verbose debug" + depends on SND_DEBUG + help + Say Y here to enable extra-verbose debugging messages. + + Let me repeat: it enables EXTRA-VERBOSE DEBUGGING messages. + So, say Y only if you are ready to be annoyed. + +config SND_PCM_XRUN_DEBUG + bool "Enable PCM ring buffer overrun/underrun debugging" + default n + depends on SND_DEBUG && SND_VERBOSE_PROCFS + help + Say Y to enable the PCM ring buffer overrun/underrun debugging. + It is usually not required, but if you have trouble with + sound clicking when system is loaded, it may help to determine + the process or driver which causes the scheduling gaps. + +config SND_CTL_INPUT_VALIDATION + bool "Validate input data to control API" + help + Say Y to enable the additional validation for the input data to + each control element, including the value range checks. + An error is returned from ALSA core for invalid inputs without + passing to the driver. This is a kind of hardening for drivers + that have no proper error checks, at the cost of a slight + performance overhead. + +config SND_CTL_DEBUG + bool "Enable debugging feature for control API" + depends on SND_DEBUG + help + Say Y to enable the debugging feature for ALSA control API. + It performs the additional sanity-checks for each control element + read access, such as whether the values returned from the driver + are in the proper ranges or the check of the invalid access at + out-of-array areas. The error is printed when the driver gives + such unexpected values. + When you develop a driver that deals with control elements, it's + strongly recommended to try this one once and verify whether you see + any relevant errors or not. + +config SND_JACK_INJECTION_DEBUG + bool "Sound jack injection interface via debugfs" + depends on SND_JACK && SND_DEBUG && DEBUG_FS + help + This option can be used to enable or disable sound jack + software injection. + Say Y if you are debugging via jack injection interface. + If unsure select "N". + +config SND_VMASTER + bool + +config SND_DMA_SGBUF + def_bool y + depends on X86 + +config SND_CTL_LED + tristate + select NEW_LEDS if SND_CTL_LED + select LEDS_TRIGGERS if SND_CTL_LED + select LEDS_TRIGGER_AUDIO if SND_CTL_LED + +source "sound/core/seq/Kconfig" diff --git a/sound/core/Makefile b/sound/core/Makefile new file mode 100644 index 0000000000..a6b444ee28 --- /dev/null +++ b/sound/core/Makefile @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 1999,2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-y := sound.o init.o memory.o control.o misc.o device.o +ifneq ($(CONFIG_SND_PROC_FS),) +snd-y += info.o +snd-$(CONFIG_SND_OSSEMUL) += info_oss.o +endif +snd-$(CONFIG_ISA_DMA_API) += isadma.o +snd-$(CONFIG_SND_OSSEMUL) += sound_oss.o +snd-$(CONFIG_SND_VMASTER) += vmaster.o +snd-$(CONFIG_SND_JACK) += ctljack.o jack.o + +snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_misc.o \ + pcm_memory.o memalloc.o +snd-pcm-$(CONFIG_SND_PCM_TIMER) += pcm_timer.o +snd-pcm-$(CONFIG_SND_PCM_ELD) += pcm_drm_eld.o +snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o + +# for trace-points +CFLAGS_pcm_lib.o := -I$(src) +CFLAGS_pcm_native.o := -I$(src) + +snd-pcm-dmaengine-objs := pcm_dmaengine.o + +snd-ctl-led-objs := control_led.o +snd-rawmidi-objs := rawmidi.o +snd-ump-objs := ump.o +snd-ump-$(CONFIG_SND_UMP_LEGACY_RAWMIDI) += ump_convert.o +snd-timer-objs := timer.o +snd-hrtimer-objs := hrtimer.o +snd-rtctimer-objs := rtctimer.o +snd-hwdep-objs := hwdep.o +snd-seq-device-objs := seq_device.o + +snd-compress-objs := compress_offload.o + +obj-$(CONFIG_SND) += snd.o +obj-$(CONFIG_SND_CTL_LED) += snd-ctl-led.o +obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o +obj-$(CONFIG_SND_TIMER) += snd-timer.o +obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o +obj-$(CONFIG_SND_PCM) += snd-pcm.o +obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o +obj-$(CONFIG_SND_SEQ_DEVICE) += snd-seq-device.o +obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o +obj-$(CONFIG_SND_UMP) += snd-ump.o + +obj-$(CONFIG_SND_OSSEMUL) += oss/ +obj-$(CONFIG_SND_SEQUENCER) += seq/ + +obj-$(CONFIG_SND_COMPRESS_OFFLOAD) += snd-compress.o diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c new file mode 100644 index 0000000000..619371aa99 --- /dev/null +++ b/sound/core/compress_offload.c @@ -0,0 +1,1209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * compress_core.c - compress offload core + * + * Copyright (C) 2011 Intel Corporation + * Authors: Vinod Koul <vinod.koul@linux.intel.com> + * Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#define FORMAT(fmt) "%s: %d: " fmt, __func__, __LINE__ +#define pr_fmt(fmt) KBUILD_MODNAME ": " FORMAT(fmt) + +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/math64.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/uio.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/compat.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/compress_params.h> +#include <sound/compress_offload.h> +#include <sound/compress_driver.h> + +/* struct snd_compr_codec_caps overflows the ioctl bit size for some + * architectures, so we need to disable the relevant ioctls. + */ +#if _IOC_SIZEBITS < 14 +#define COMPR_CODEC_CAPS_OVERFLOW +#endif + +/* TODO: + * - add substream support for multiple devices in case of + * SND_DYNAMIC_MINORS is not used + * - Multiple node representation + * driver should be able to register multiple nodes + */ + +struct snd_compr_file { + unsigned long caps; + struct snd_compr_stream stream; +}; + +static void error_delayed_work(struct work_struct *work); + +/* + * a note on stream states used: + * we use following states in the compressed core + * SNDRV_PCM_STATE_OPEN: When stream has been opened. + * SNDRV_PCM_STATE_SETUP: When stream has been initialized. This is done by + * calling SNDRV_COMPRESS_SET_PARAMS. Running streams will come to this + * state at stop by calling SNDRV_COMPRESS_STOP, or at end of drain. + * SNDRV_PCM_STATE_PREPARED: When a stream has been written to (for + * playback only). User after setting up stream writes the data buffer + * before starting the stream. + * SNDRV_PCM_STATE_RUNNING: When stream has been started and is + * decoding/encoding and rendering/capturing data. + * SNDRV_PCM_STATE_DRAINING: When stream is draining current data. This is done + * by calling SNDRV_COMPRESS_DRAIN. + * SNDRV_PCM_STATE_PAUSED: When stream is paused. This is done by calling + * SNDRV_COMPRESS_PAUSE. It can be stopped or resumed by calling + * SNDRV_COMPRESS_STOP or SNDRV_COMPRESS_RESUME respectively. + */ +static int snd_compr_open(struct inode *inode, struct file *f) +{ + struct snd_compr *compr; + struct snd_compr_file *data; + struct snd_compr_runtime *runtime; + enum snd_compr_direction dirn; + int maj = imajor(inode); + int ret; + + if ((f->f_flags & O_ACCMODE) == O_WRONLY) + dirn = SND_COMPRESS_PLAYBACK; + else if ((f->f_flags & O_ACCMODE) == O_RDONLY) + dirn = SND_COMPRESS_CAPTURE; + else + return -EINVAL; + + if (maj == snd_major) + compr = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_COMPRESS); + else + return -EBADFD; + + if (compr == NULL) { + pr_err("no device data!!!\n"); + return -ENODEV; + } + + if (dirn != compr->direction) { + pr_err("this device doesn't support this direction\n"); + snd_card_unref(compr->card); + return -EINVAL; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + snd_card_unref(compr->card); + return -ENOMEM; + } + + INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); + + data->stream.ops = compr->ops; + data->stream.direction = dirn; + data->stream.private_data = compr->private_data; + data->stream.device = compr; + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) { + kfree(data); + snd_card_unref(compr->card); + return -ENOMEM; + } + runtime->state = SNDRV_PCM_STATE_OPEN; + init_waitqueue_head(&runtime->sleep); + data->stream.runtime = runtime; + f->private_data = (void *)data; + mutex_lock(&compr->lock); + ret = compr->ops->open(&data->stream); + mutex_unlock(&compr->lock); + if (ret) { + kfree(runtime); + kfree(data); + } + snd_card_unref(compr->card); + return ret; +} + +static int snd_compr_free(struct inode *inode, struct file *f) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_runtime *runtime = data->stream.runtime; + + cancel_delayed_work_sync(&data->stream.error_work); + + switch (runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_PAUSED: + data->stream.ops->trigger(&data->stream, SNDRV_PCM_TRIGGER_STOP); + break; + default: + break; + } + + data->stream.ops->free(&data->stream); + if (!data->stream.runtime->dma_buffer_p) + kfree(data->stream.runtime->buffer); + kfree(data->stream.runtime); + kfree(data); + return 0; +} + +static int snd_compr_update_tstamp(struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + if (!stream->ops->pointer) + return -ENOTSUPP; + stream->ops->pointer(stream, tstamp); + pr_debug("dsp consumed till %d total %d bytes\n", + tstamp->byte_offset, tstamp->copied_total); + if (stream->direction == SND_COMPRESS_PLAYBACK) + stream->runtime->total_bytes_transferred = tstamp->copied_total; + else + stream->runtime->total_bytes_available = tstamp->copied_total; + return 0; +} + +static size_t snd_compr_calc_avail(struct snd_compr_stream *stream, + struct snd_compr_avail *avail) +{ + memset(avail, 0, sizeof(*avail)); + snd_compr_update_tstamp(stream, &avail->tstamp); + /* Still need to return avail even if tstamp can't be filled in */ + + if (stream->runtime->total_bytes_available == 0 && + stream->runtime->state == SNDRV_PCM_STATE_SETUP && + stream->direction == SND_COMPRESS_PLAYBACK) { + pr_debug("detected init and someone forgot to do a write\n"); + return stream->runtime->buffer_size; + } + pr_debug("app wrote %lld, DSP consumed %lld\n", + stream->runtime->total_bytes_available, + stream->runtime->total_bytes_transferred); + if (stream->runtime->total_bytes_available == + stream->runtime->total_bytes_transferred) { + if (stream->direction == SND_COMPRESS_PLAYBACK) { + pr_debug("both pointers are same, returning full avail\n"); + return stream->runtime->buffer_size; + } else { + pr_debug("both pointers are same, returning no avail\n"); + return 0; + } + } + + avail->avail = stream->runtime->total_bytes_available - + stream->runtime->total_bytes_transferred; + if (stream->direction == SND_COMPRESS_PLAYBACK) + avail->avail = stream->runtime->buffer_size - avail->avail; + + pr_debug("ret avail as %lld\n", avail->avail); + return avail->avail; +} + +static inline size_t snd_compr_get_avail(struct snd_compr_stream *stream) +{ + struct snd_compr_avail avail; + + return snd_compr_calc_avail(stream, &avail); +} + +static int +snd_compr_ioctl_avail(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_avail ioctl_avail; + size_t avail; + + avail = snd_compr_calc_avail(stream, &ioctl_avail); + ioctl_avail.avail = avail; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + return -EBADFD; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + + if (copy_to_user((__u64 __user *)arg, + &ioctl_avail, sizeof(ioctl_avail))) + return -EFAULT; + return 0; +} + +static int snd_compr_write_data(struct snd_compr_stream *stream, + const char __user *buf, size_t count) +{ + void *dstn; + size_t copy; + struct snd_compr_runtime *runtime = stream->runtime; + /* 64-bit Modulus */ + u64 app_pointer = div64_u64(runtime->total_bytes_available, + runtime->buffer_size); + app_pointer = runtime->total_bytes_available - + (app_pointer * runtime->buffer_size); + + dstn = runtime->buffer + app_pointer; + pr_debug("copying %ld at %lld\n", + (unsigned long)count, app_pointer); + if (count < runtime->buffer_size - app_pointer) { + if (copy_from_user(dstn, buf, count)) + return -EFAULT; + } else { + copy = runtime->buffer_size - app_pointer; + if (copy_from_user(dstn, buf, copy)) + return -EFAULT; + if (copy_from_user(runtime->buffer, buf + copy, count - copy)) + return -EFAULT; + } + /* if DSP cares, let it know data has been written */ + if (stream->ops->ack) + stream->ops->ack(stream, count); + return count; +} + +static ssize_t snd_compr_write(struct file *f, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + size_t avail; + int retval; + + if (snd_BUG_ON(!data)) + return -EFAULT; + + stream = &data->stream; + mutex_lock(&stream->device->lock); + /* write is allowed when stream is running or has been steup */ + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_RUNNING: + break; + default: + mutex_unlock(&stream->device->lock); + return -EBADFD; + } + + avail = snd_compr_get_avail(stream); + pr_debug("avail returned %ld\n", (unsigned long)avail); + /* calculate how much we can write to buffer */ + if (avail > count) + avail = count; + + if (stream->ops->copy) { + char __user* cbuf = (char __user*)buf; + retval = stream->ops->copy(stream, cbuf, avail); + } else { + retval = snd_compr_write_data(stream, buf, avail); + } + if (retval > 0) + stream->runtime->total_bytes_available += retval; + + /* while initiating the stream, write should be called before START + * call, so in setup move state */ + if (stream->runtime->state == SNDRV_PCM_STATE_SETUP) { + stream->runtime->state = SNDRV_PCM_STATE_PREPARED; + pr_debug("stream prepared, Houston we are good to go\n"); + } + + mutex_unlock(&stream->device->lock); + return retval; +} + + +static ssize_t snd_compr_read(struct file *f, char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + size_t avail; + int retval; + + if (snd_BUG_ON(!data)) + return -EFAULT; + + stream = &data->stream; + mutex_lock(&stream->device->lock); + + /* read is allowed when stream is running, paused, draining and setup + * (yes setup is state which we transition to after stop, so if user + * wants to read data after stop we allow that) + */ + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + case SNDRV_PCM_STATE_DISCONNECTED: + retval = -EBADFD; + goto out; + case SNDRV_PCM_STATE_XRUN: + retval = -EPIPE; + goto out; + } + + avail = snd_compr_get_avail(stream); + pr_debug("avail returned %ld\n", (unsigned long)avail); + /* calculate how much we can read from buffer */ + if (avail > count) + avail = count; + + if (stream->ops->copy) { + retval = stream->ops->copy(stream, buf, avail); + } else { + retval = -ENXIO; + goto out; + } + if (retval > 0) + stream->runtime->total_bytes_transferred += retval; + +out: + mutex_unlock(&stream->device->lock); + return retval; +} + +static int snd_compr_mmap(struct file *f, struct vm_area_struct *vma) +{ + return -ENXIO; +} + +static __poll_t snd_compr_get_poll(struct snd_compr_stream *stream) +{ + if (stream->direction == SND_COMPRESS_PLAYBACK) + return EPOLLOUT | EPOLLWRNORM; + else + return EPOLLIN | EPOLLRDNORM; +} + +static __poll_t snd_compr_poll(struct file *f, poll_table *wait) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + size_t avail; + __poll_t retval = 0; + + if (snd_BUG_ON(!data)) + return EPOLLERR; + + stream = &data->stream; + + mutex_lock(&stream->device->lock); + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_XRUN: + retval = snd_compr_get_poll(stream) | EPOLLERR; + goto out; + default: + break; + } + + poll_wait(f, &stream->runtime->sleep, wait); + + avail = snd_compr_get_avail(stream); + pr_debug("avail is %ld\n", (unsigned long)avail); + /* check if we have at least one fragment to fill */ + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_DRAINING: + /* stream has been woken up after drain is complete + * draining done so set stream state to stopped + */ + retval = snd_compr_get_poll(stream); + stream->runtime->state = SNDRV_PCM_STATE_SETUP; + break; + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + if (avail >= stream->runtime->fragment_size) + retval = snd_compr_get_poll(stream); + break; + default: + retval = snd_compr_get_poll(stream) | EPOLLERR; + break; + } +out: + mutex_unlock(&stream->device->lock); + return retval; +} + +static int +snd_compr_get_caps(struct snd_compr_stream *stream, unsigned long arg) +{ + int retval; + struct snd_compr_caps caps; + + if (!stream->ops->get_caps) + return -ENXIO; + + memset(&caps, 0, sizeof(caps)); + retval = stream->ops->get_caps(stream, &caps); + if (retval) + goto out; + if (copy_to_user((void __user *)arg, &caps, sizeof(caps))) + retval = -EFAULT; +out: + return retval; +} + +#ifndef COMPR_CODEC_CAPS_OVERFLOW +static int +snd_compr_get_codec_caps(struct snd_compr_stream *stream, unsigned long arg) +{ + int retval; + struct snd_compr_codec_caps *caps; + + if (!stream->ops->get_codec_caps) + return -ENXIO; + + caps = kzalloc(sizeof(*caps), GFP_KERNEL); + if (!caps) + return -ENOMEM; + + retval = stream->ops->get_codec_caps(stream, caps); + if (retval) + goto out; + if (copy_to_user((void __user *)arg, caps, sizeof(*caps))) + retval = -EFAULT; + +out: + kfree(caps); + return retval; +} +#endif /* !COMPR_CODEC_CAPS_OVERFLOW */ + +int snd_compr_malloc_pages(struct snd_compr_stream *stream, size_t size) +{ + struct snd_dma_buffer *dmab; + int ret; + + if (snd_BUG_ON(!(stream) || !(stream)->runtime)) + return -EINVAL; + dmab = kzalloc(sizeof(*dmab), GFP_KERNEL); + if (!dmab) + return -ENOMEM; + dmab->dev = stream->dma_buffer.dev; + ret = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev, size, dmab); + if (ret < 0) { + kfree(dmab); + return ret; + } + + snd_compr_set_runtime_buffer(stream, dmab); + stream->runtime->dma_bytes = size; + return 1; +} +EXPORT_SYMBOL(snd_compr_malloc_pages); + +int snd_compr_free_pages(struct snd_compr_stream *stream) +{ + struct snd_compr_runtime *runtime; + + if (snd_BUG_ON(!(stream) || !(stream)->runtime)) + return -EINVAL; + runtime = stream->runtime; + if (runtime->dma_area == NULL) + return 0; + if (runtime->dma_buffer_p != &stream->dma_buffer) { + /* It's a newly allocated buffer. Release it now. */ + snd_dma_free_pages(runtime->dma_buffer_p); + kfree(runtime->dma_buffer_p); + } + + snd_compr_set_runtime_buffer(stream, NULL); + return 0; +} +EXPORT_SYMBOL(snd_compr_free_pages); + +/* revisit this with snd_pcm_preallocate_xxx */ +static int snd_compr_allocate_buffer(struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + unsigned int buffer_size; + void *buffer = NULL; + + buffer_size = params->buffer.fragment_size * params->buffer.fragments; + if (stream->ops->copy) { + buffer = NULL; + /* if copy is defined the driver will be required to copy + * the data from core + */ + } else { + if (stream->runtime->dma_buffer_p) { + + if (buffer_size > stream->runtime->dma_buffer_p->bytes) + dev_err(stream->device->dev, + "Not enough DMA buffer"); + else + buffer = stream->runtime->dma_buffer_p->area; + + } else { + buffer = kmalloc(buffer_size, GFP_KERNEL); + } + + if (!buffer) + return -ENOMEM; + } + stream->runtime->fragment_size = params->buffer.fragment_size; + stream->runtime->fragments = params->buffer.fragments; + stream->runtime->buffer = buffer; + stream->runtime->buffer_size = buffer_size; + return 0; +} + +static int snd_compress_check_input(struct snd_compr_params *params) +{ + /* first let's check the buffer parameter's */ + if (params->buffer.fragment_size == 0 || + params->buffer.fragments > U32_MAX / params->buffer.fragment_size || + params->buffer.fragments == 0) + return -EINVAL; + + /* now codec parameters */ + if (params->codec.id == 0 || params->codec.id > SND_AUDIOCODEC_MAX) + return -EINVAL; + + if (params->codec.ch_in == 0 || params->codec.ch_out == 0) + return -EINVAL; + + return 0; +} + +static int +snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_params *params; + int retval; + + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN || stream->next_track) { + /* + * we should allow parameter change only when stream has been + * opened not in other cases + */ + params = memdup_user((void __user *)arg, sizeof(*params)); + if (IS_ERR(params)) + return PTR_ERR(params); + + retval = snd_compress_check_input(params); + if (retval) + goto out; + + retval = snd_compr_allocate_buffer(stream, params); + if (retval) { + retval = -ENOMEM; + goto out; + } + + retval = stream->ops->set_params(stream, params); + if (retval) + goto out; + + if (stream->next_track) + goto out; + + stream->metadata_set = false; + stream->next_track = false; + + stream->runtime->state = SNDRV_PCM_STATE_SETUP; + } else { + return -EPERM; + } +out: + kfree(params); + return retval; +} + +static int +snd_compr_get_params(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_codec *params; + int retval; + + if (!stream->ops->get_params) + return -EBADFD; + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + retval = stream->ops->get_params(stream, params); + if (retval) + goto out; + if (copy_to_user((char __user *)arg, params, sizeof(*params))) + retval = -EFAULT; + +out: + kfree(params); + return retval; +} + +static int +snd_compr_get_metadata(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_metadata metadata; + int retval; + + if (!stream->ops->get_metadata) + return -ENXIO; + + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) + return -EFAULT; + + retval = stream->ops->get_metadata(stream, &metadata); + if (retval != 0) + return retval; + + if (copy_to_user((void __user *)arg, &metadata, sizeof(metadata))) + return -EFAULT; + + return 0; +} + +static int +snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_metadata metadata; + int retval; + + if (!stream->ops->set_metadata) + return -ENXIO; + /* + * we should allow parameter change only when stream has been + * opened not in other cases + */ + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata))) + return -EFAULT; + + retval = stream->ops->set_metadata(stream, &metadata); + stream->metadata_set = true; + + return retval; +} + +static inline int +snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg) +{ + struct snd_compr_tstamp tstamp = {0}; + int ret; + + ret = snd_compr_update_tstamp(stream, &tstamp); + if (ret == 0) + ret = copy_to_user((struct snd_compr_tstamp __user *)arg, + &tstamp, sizeof(tstamp)) ? -EFAULT : 0; + return ret; +} + +static int snd_compr_pause(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_PAUSED; + break; + case SNDRV_PCM_STATE_DRAINING: + if (!stream->device->use_pause_in_draining) + return -EPERM; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_PUSH); + if (!retval) + stream->pause_in_draining = true; + break; + default: + return -EPERM; + } + return retval; +} + +static int snd_compr_resume(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_PAUSED: + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_RELEASE); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_RUNNING; + break; + case SNDRV_PCM_STATE_DRAINING: + if (!stream->pause_in_draining) + return -EPERM; + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_PAUSE_RELEASE); + if (!retval) + stream->pause_in_draining = false; + break; + default: + return -EPERM; + } + return retval; +} + +static int snd_compr_start(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_SETUP: + if (stream->direction != SND_COMPRESS_CAPTURE) + return -EPERM; + break; + case SNDRV_PCM_STATE_PREPARED: + break; + default: + return -EPERM; + } + + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_START); + if (!retval) + stream->runtime->state = SNDRV_PCM_STATE_RUNNING; + return retval; +} + +static int snd_compr_stop(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + return -EPERM; + default: + break; + } + + retval = stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + if (!retval) { + /* clear flags and stop any drain wait */ + stream->partial_drain = false; + stream->metadata_set = false; + stream->pause_in_draining = false; + snd_compr_drain_notify(stream); + stream->runtime->total_bytes_available = 0; + stream->runtime->total_bytes_transferred = 0; + } + return retval; +} + +static void error_delayed_work(struct work_struct *work) +{ + struct snd_compr_stream *stream; + + stream = container_of(work, struct snd_compr_stream, error_work.work); + + mutex_lock(&stream->device->lock); + + stream->ops->trigger(stream, SNDRV_PCM_TRIGGER_STOP); + wake_up(&stream->runtime->sleep); + + mutex_unlock(&stream->device->lock); +} + +/** + * snd_compr_stop_error: Report a fatal error on a stream + * @stream: pointer to stream + * @state: state to transition the stream to + * + * Stop the stream and set its state. + * + * Should be called with compressed device lock held. + * + * Return: zero if successful, or a negative error code + */ +int snd_compr_stop_error(struct snd_compr_stream *stream, + snd_pcm_state_t state) +{ + if (stream->runtime->state == state) + return 0; + + stream->runtime->state = state; + + pr_debug("Changing state to: %d\n", state); + + queue_delayed_work(system_power_efficient_wq, &stream->error_work, 0); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_compr_stop_error); + +static int snd_compress_wait_for_drain(struct snd_compr_stream *stream) +{ + int ret; + + /* + * We are called with lock held. So drop the lock while we wait for + * drain complete notification from the driver + * + * It is expected that driver will notify the drain completion and then + * stream will be moved to SETUP state, even if draining resulted in an + * error. We can trigger next track after this. + */ + stream->runtime->state = SNDRV_PCM_STATE_DRAINING; + mutex_unlock(&stream->device->lock); + + /* we wait for drain to complete here, drain can return when + * interruption occurred, wait returned error or success. + * For the first two cases we don't do anything different here and + * return after waking up + */ + + ret = wait_event_interruptible(stream->runtime->sleep, + (stream->runtime->state != SNDRV_PCM_STATE_DRAINING)); + if (ret == -ERESTARTSYS) + pr_debug("wait aborted by a signal\n"); + else if (ret) + pr_debug("wait for drain failed with %d\n", ret); + + + wake_up(&stream->runtime->sleep); + mutex_lock(&stream->device->lock); + + return ret; +} + +static int snd_compr_drain(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + return -EPERM; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_DRAIN); + if (retval) { + pr_debug("SND_COMPR_TRIGGER_DRAIN failed %d\n", retval); + wake_up(&stream->runtime->sleep); + return retval; + } + + return snd_compress_wait_for_drain(stream); +} + +static int snd_compr_next_track(struct snd_compr_stream *stream) +{ + int retval; + + /* only a running stream can transition to next track */ + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING) + return -EPERM; + + /* next track doesn't have any meaning for capture streams */ + if (stream->direction == SND_COMPRESS_CAPTURE) + return -EPERM; + + /* you can signal next track if this is intended to be a gapless stream + * and current track metadata is set + */ + if (stream->metadata_set == false) + return -EPERM; + + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_NEXT_TRACK); + if (retval != 0) + return retval; + stream->metadata_set = false; + stream->next_track = true; + return 0; +} + +static int snd_compr_partial_drain(struct snd_compr_stream *stream) +{ + int retval; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + return -EPERM; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + break; + } + + /* partial drain doesn't have any meaning for capture streams */ + if (stream->direction == SND_COMPRESS_CAPTURE) + return -EPERM; + + /* stream can be drained only when next track has been signalled */ + if (stream->next_track == false) + return -EPERM; + + stream->partial_drain = true; + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_PARTIAL_DRAIN); + if (retval) { + pr_debug("Partial drain returned failure\n"); + wake_up(&stream->runtime->sleep); + return retval; + } + + stream->next_track = false; + return snd_compress_wait_for_drain(stream); +} + +static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct snd_compr_file *data = f->private_data; + struct snd_compr_stream *stream; + int retval = -ENOTTY; + + if (snd_BUG_ON(!data)) + return -EFAULT; + + stream = &data->stream; + + mutex_lock(&stream->device->lock); + switch (_IOC_NR(cmd)) { + case _IOC_NR(SNDRV_COMPRESS_IOCTL_VERSION): + retval = put_user(SNDRV_COMPRESS_VERSION, + (int __user *)arg) ? -EFAULT : 0; + break; + case _IOC_NR(SNDRV_COMPRESS_GET_CAPS): + retval = snd_compr_get_caps(stream, arg); + break; +#ifndef COMPR_CODEC_CAPS_OVERFLOW + case _IOC_NR(SNDRV_COMPRESS_GET_CODEC_CAPS): + retval = snd_compr_get_codec_caps(stream, arg); + break; +#endif + case _IOC_NR(SNDRV_COMPRESS_SET_PARAMS): + retval = snd_compr_set_params(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_GET_PARAMS): + retval = snd_compr_get_params(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_SET_METADATA): + retval = snd_compr_set_metadata(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_GET_METADATA): + retval = snd_compr_get_metadata(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_TSTAMP): + retval = snd_compr_tstamp(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_AVAIL): + retval = snd_compr_ioctl_avail(stream, arg); + break; + case _IOC_NR(SNDRV_COMPRESS_PAUSE): + retval = snd_compr_pause(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_RESUME): + retval = snd_compr_resume(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_START): + retval = snd_compr_start(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_STOP): + retval = snd_compr_stop(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_DRAIN): + retval = snd_compr_drain(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_PARTIAL_DRAIN): + retval = snd_compr_partial_drain(stream); + break; + case _IOC_NR(SNDRV_COMPRESS_NEXT_TRACK): + retval = snd_compr_next_track(stream); + break; + + } + mutex_unlock(&stream->device->lock); + return retval; +} + +/* support of 32bit userspace on 64bit platforms */ +#ifdef CONFIG_COMPAT +static long snd_compr_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return snd_compr_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static const struct file_operations snd_compr_file_ops = { + .owner = THIS_MODULE, + .open = snd_compr_open, + .release = snd_compr_free, + .write = snd_compr_write, + .read = snd_compr_read, + .unlocked_ioctl = snd_compr_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = snd_compr_ioctl_compat, +#endif + .mmap = snd_compr_mmap, + .poll = snd_compr_poll, +}; + +static int snd_compress_dev_register(struct snd_device *device) +{ + int ret; + struct snd_compr *compr; + + if (snd_BUG_ON(!device || !device->device_data)) + return -EBADFD; + compr = device->device_data; + + pr_debug("reg device %s, direction %d\n", compr->name, + compr->direction); + /* register compressed device */ + ret = snd_register_device(SNDRV_DEVICE_TYPE_COMPRESS, + compr->card, compr->device, + &snd_compr_file_ops, compr, compr->dev); + if (ret < 0) { + pr_err("snd_register_device failed %d\n", ret); + return ret; + } + return ret; + +} + +static int snd_compress_dev_disconnect(struct snd_device *device) +{ + struct snd_compr *compr; + + compr = device->device_data; + snd_unregister_device(compr->dev); + return 0; +} + +#ifdef CONFIG_SND_VERBOSE_PROCFS +static void snd_compress_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_compr *compr = (struct snd_compr *)entry->private_data; + + snd_iprintf(buffer, "card: %d\n", compr->card->number); + snd_iprintf(buffer, "device: %d\n", compr->device); + snd_iprintf(buffer, "stream: %s\n", + compr->direction == SND_COMPRESS_PLAYBACK + ? "PLAYBACK" : "CAPTURE"); + snd_iprintf(buffer, "id: %s\n", compr->id); +} + +static int snd_compress_proc_init(struct snd_compr *compr) +{ + struct snd_info_entry *entry; + char name[16]; + + sprintf(name, "compr%i", compr->device); + entry = snd_info_create_card_entry(compr->card, name, + compr->card->proc_root); + if (!entry) + return -ENOMEM; + entry->mode = S_IFDIR | 0555; + compr->proc_root = entry; + + entry = snd_info_create_card_entry(compr->card, "info", + compr->proc_root); + if (entry) + snd_info_set_text_ops(entry, compr, + snd_compress_proc_info_read); + compr->proc_info_entry = entry; + + return 0; +} + +static void snd_compress_proc_done(struct snd_compr *compr) +{ + snd_info_free_entry(compr->proc_info_entry); + compr->proc_info_entry = NULL; + snd_info_free_entry(compr->proc_root); + compr->proc_root = NULL; +} + +static inline void snd_compress_set_id(struct snd_compr *compr, const char *id) +{ + strscpy(compr->id, id, sizeof(compr->id)); +} +#else +static inline int snd_compress_proc_init(struct snd_compr *compr) +{ + return 0; +} + +static inline void snd_compress_proc_done(struct snd_compr *compr) +{ +} + +static inline void snd_compress_set_id(struct snd_compr *compr, const char *id) +{ +} +#endif + +static int snd_compress_dev_free(struct snd_device *device) +{ + struct snd_compr *compr; + + compr = device->device_data; + snd_compress_proc_done(compr); + put_device(compr->dev); + return 0; +} + +/** + * snd_compress_new: create new compress device + * @card: sound card pointer + * @device: device number + * @dirn: device direction, should be of type enum snd_compr_direction + * @id: ID string + * @compr: compress device pointer + * + * Return: zero if successful, or a negative error code + */ +int snd_compress_new(struct snd_card *card, int device, + int dirn, const char *id, struct snd_compr *compr) +{ + static const struct snd_device_ops ops = { + .dev_free = snd_compress_dev_free, + .dev_register = snd_compress_dev_register, + .dev_disconnect = snd_compress_dev_disconnect, + }; + int ret; + + compr->card = card; + compr->device = device; + compr->direction = dirn; + mutex_init(&compr->lock); + + snd_compress_set_id(compr, id); + + ret = snd_device_alloc(&compr->dev, card); + if (ret) + return ret; + dev_set_name(compr->dev, "comprC%iD%i", card->number, device); + + ret = snd_device_new(card, SNDRV_DEV_COMPRESS, compr, &ops); + if (ret == 0) + snd_compress_proc_init(compr); + else + put_device(compr->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_compress_new); + +MODULE_DESCRIPTION("ALSA Compressed offload framework"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/core/control.c b/sound/core/control.c new file mode 100644 index 0000000000..59c8658966 --- /dev/null +++ b/sound/core/control.c @@ -0,0 +1,2562 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Routines for driver control interface + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/threads.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/math64.h> +#include <linux/sched/signal.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <sound/control.h> + +// Max allocation size for user controls. +static int max_user_ctl_alloc_size = 8 * 1024 * 1024; +module_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444); +MODULE_PARM_DESC(max_user_ctl_alloc_size, "Max allocation size for user controls"); + +#define MAX_CONTROL_COUNT 1028 + +struct snd_kctl_ioctl { + struct list_head list; /* list of all ioctls */ + snd_kctl_ioctl_func_t fioctl; +}; + +static DECLARE_RWSEM(snd_ioctl_rwsem); +static DECLARE_RWSEM(snd_ctl_layer_rwsem); +static LIST_HEAD(snd_control_ioctls); +#ifdef CONFIG_COMPAT +static LIST_HEAD(snd_control_compat_ioctls); +#endif +static struct snd_ctl_layer_ops *snd_ctl_layer; + +static int snd_ctl_remove_locked(struct snd_card *card, + struct snd_kcontrol *kcontrol); + +static int snd_ctl_open(struct inode *inode, struct file *file) +{ + unsigned long flags; + struct snd_card *card; + struct snd_ctl_file *ctl; + int i, err; + + err = stream_open(inode, file); + if (err < 0) + return err; + + card = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_CONTROL); + if (!card) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(card, file); + if (err < 0) { + err = -ENODEV; + goto __error1; + } + if (!try_module_get(card->module)) { + err = -EFAULT; + goto __error2; + } + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (ctl == NULL) { + err = -ENOMEM; + goto __error; + } + INIT_LIST_HEAD(&ctl->events); + init_waitqueue_head(&ctl->change_sleep); + spin_lock_init(&ctl->read_lock); + ctl->card = card; + for (i = 0; i < SND_CTL_SUBDEV_ITEMS; i++) + ctl->preferred_subdevice[i] = -1; + ctl->pid = get_pid(task_pid(current)); + file->private_data = ctl; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_add_tail(&ctl->list, &card->ctl_files); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + snd_card_unref(card); + return 0; + + __error: + module_put(card->module); + __error2: + snd_card_file_remove(card, file); + __error1: + if (card) + snd_card_unref(card); + return err; +} + +static void snd_ctl_empty_read_queue(struct snd_ctl_file * ctl) +{ + unsigned long flags; + struct snd_kctl_event *cread; + + spin_lock_irqsave(&ctl->read_lock, flags); + while (!list_empty(&ctl->events)) { + cread = snd_kctl_event(ctl->events.next); + list_del(&cread->list); + kfree(cread); + } + spin_unlock_irqrestore(&ctl->read_lock, flags); +} + +static int snd_ctl_release(struct inode *inode, struct file *file) +{ + unsigned long flags; + struct snd_card *card; + struct snd_ctl_file *ctl; + struct snd_kcontrol *control; + unsigned int idx; + + ctl = file->private_data; + file->private_data = NULL; + card = ctl->card; + write_lock_irqsave(&card->ctl_files_rwlock, flags); + list_del(&ctl->list); + write_unlock_irqrestore(&card->ctl_files_rwlock, flags); + down_write(&card->controls_rwsem); + list_for_each_entry(control, &card->controls, list) + for (idx = 0; idx < control->count; idx++) + if (control->vd[idx].owner == ctl) + control->vd[idx].owner = NULL; + up_write(&card->controls_rwsem); + snd_fasync_free(ctl->fasync); + snd_ctl_empty_read_queue(ctl); + put_pid(ctl->pid); + kfree(ctl); + module_put(card->module); + snd_card_file_remove(card, file); + return 0; +} + +/** + * snd_ctl_notify - Send notification to user-space for a control change + * @card: the card to send notification + * @mask: the event mask, SNDRV_CTL_EVENT_* + * @id: the ctl element id to send notification + * + * This function adds an event record with the given id and mask, appends + * to the list and wakes up the user-space for notification. This can be + * called in the atomic context. + */ +void snd_ctl_notify(struct snd_card *card, unsigned int mask, + struct snd_ctl_elem_id *id) +{ + unsigned long flags; + struct snd_ctl_file *ctl; + struct snd_kctl_event *ev; + + if (snd_BUG_ON(!card || !id)) + return; + if (card->shutdown) + return; + read_lock_irqsave(&card->ctl_files_rwlock, flags); +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + card->mixer_oss_change_count++; +#endif + list_for_each_entry(ctl, &card->ctl_files, list) { + if (!ctl->subscribed) + continue; + spin_lock(&ctl->read_lock); + list_for_each_entry(ev, &ctl->events, list) { + if (ev->id.numid == id->numid) { + ev->mask |= mask; + goto _found; + } + } + ev = kzalloc(sizeof(*ev), GFP_ATOMIC); + if (ev) { + ev->id = *id; + ev->mask = mask; + list_add_tail(&ev->list, &ctl->events); + } else { + dev_err(card->dev, "No memory available to allocate event\n"); + } + _found: + wake_up(&ctl->change_sleep); + spin_unlock(&ctl->read_lock); + snd_kill_fasync(ctl->fasync, SIGIO, POLL_IN); + } + read_unlock_irqrestore(&card->ctl_files_rwlock, flags); +} +EXPORT_SYMBOL(snd_ctl_notify); + +/** + * snd_ctl_notify_one - Send notification to user-space for a control change + * @card: the card to send notification + * @mask: the event mask, SNDRV_CTL_EVENT_* + * @kctl: the pointer with the control instance + * @ioff: the additional offset to the control index + * + * This function calls snd_ctl_notify() and does additional jobs + * like LED state changes. + */ +void snd_ctl_notify_one(struct snd_card *card, unsigned int mask, + struct snd_kcontrol *kctl, unsigned int ioff) +{ + struct snd_ctl_elem_id id = kctl->id; + struct snd_ctl_layer_ops *lops; + + id.index += ioff; + id.numid += ioff; + snd_ctl_notify(card, mask, &id); + down_read(&snd_ctl_layer_rwsem); + for (lops = snd_ctl_layer; lops; lops = lops->next) + lops->lnotify(card, mask, kctl, ioff); + up_read(&snd_ctl_layer_rwsem); +} +EXPORT_SYMBOL(snd_ctl_notify_one); + +/** + * snd_ctl_new - create a new control instance with some elements + * @kctl: the pointer to store new control instance + * @count: the number of elements in this control + * @access: the default access flags for elements in this control + * @file: given when locking these elements + * + * Allocates a memory object for a new control instance. The instance has + * elements as many as the given number (@count). Each element has given + * access permissions (@access). Each element is locked when @file is given. + * + * Return: 0 on success, error code on failure + */ +static int snd_ctl_new(struct snd_kcontrol **kctl, unsigned int count, + unsigned int access, struct snd_ctl_file *file) +{ + unsigned int idx; + + if (count == 0 || count > MAX_CONTROL_COUNT) + return -EINVAL; + + *kctl = kzalloc(struct_size(*kctl, vd, count), GFP_KERNEL); + if (!*kctl) + return -ENOMEM; + + for (idx = 0; idx < count; idx++) { + (*kctl)->vd[idx].access = access; + (*kctl)->vd[idx].owner = file; + } + (*kctl)->count = count; + + return 0; +} + +/** + * snd_ctl_new1 - create a control instance from the template + * @ncontrol: the initialization record + * @private_data: the private data to set + * + * Allocates a new struct snd_kcontrol instance and initialize from the given + * template. When the access field of ncontrol is 0, it's assumed as + * READWRITE access. When the count field is 0, it's assumes as one. + * + * Return: The pointer of the newly generated instance, or %NULL on failure. + */ +struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol, + void *private_data) +{ + struct snd_kcontrol *kctl; + unsigned int count; + unsigned int access; + int err; + + if (snd_BUG_ON(!ncontrol || !ncontrol->info)) + return NULL; + + count = ncontrol->count; + if (count == 0) + count = 1; + + access = ncontrol->access; + if (access == 0) + access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE | + SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK | + SNDRV_CTL_ELEM_ACCESS_LED_MASK | + SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK); + + err = snd_ctl_new(&kctl, count, access, NULL); + if (err < 0) + return NULL; + + /* The 'numid' member is decided when calling snd_ctl_add(). */ + kctl->id.iface = ncontrol->iface; + kctl->id.device = ncontrol->device; + kctl->id.subdevice = ncontrol->subdevice; + if (ncontrol->name) { + strscpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name)); + if (strcmp(ncontrol->name, kctl->id.name) != 0) + pr_warn("ALSA: Control name '%s' truncated to '%s'\n", + ncontrol->name, kctl->id.name); + } + kctl->id.index = ncontrol->index; + + kctl->info = ncontrol->info; + kctl->get = ncontrol->get; + kctl->put = ncontrol->put; + kctl->tlv.p = ncontrol->tlv.p; + + kctl->private_value = ncontrol->private_value; + kctl->private_data = private_data; + + return kctl; +} +EXPORT_SYMBOL(snd_ctl_new1); + +/** + * snd_ctl_free_one - release the control instance + * @kcontrol: the control instance + * + * Releases the control instance created via snd_ctl_new() + * or snd_ctl_new1(). + * Don't call this after the control was added to the card. + */ +void snd_ctl_free_one(struct snd_kcontrol *kcontrol) +{ + if (kcontrol) { + if (kcontrol->private_free) + kcontrol->private_free(kcontrol); + kfree(kcontrol); + } +} +EXPORT_SYMBOL(snd_ctl_free_one); + +static bool snd_ctl_remove_numid_conflict(struct snd_card *card, + unsigned int count) +{ + struct snd_kcontrol *kctl; + + /* Make sure that the ids assigned to the control do not wrap around */ + if (card->last_numid >= UINT_MAX - count) + card->last_numid = 0; + + list_for_each_entry(kctl, &card->controls, list) { + if (kctl->id.numid < card->last_numid + 1 + count && + kctl->id.numid + kctl->count > card->last_numid + 1) { + card->last_numid = kctl->id.numid + kctl->count - 1; + return true; + } + } + return false; +} + +static int snd_ctl_find_hole(struct snd_card *card, unsigned int count) +{ + unsigned int iter = 100000; + + while (snd_ctl_remove_numid_conflict(card, count)) { + if (--iter == 0) { + /* this situation is very unlikely */ + dev_err(card->dev, "unable to allocate new control numid\n"); + return -ENOMEM; + } + } + return 0; +} + +/* check whether the given id is contained in the given kctl */ +static bool elem_id_matches(const struct snd_kcontrol *kctl, + const struct snd_ctl_elem_id *id) +{ + return kctl->id.iface == id->iface && + kctl->id.device == id->device && + kctl->id.subdevice == id->subdevice && + !strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)) && + kctl->id.index <= id->index && + kctl->id.index + kctl->count > id->index; +} + +#ifdef CONFIG_SND_CTL_FAST_LOOKUP +/* Compute a hash key for the corresponding ctl id + * It's for the name lookup, hence the numid is excluded. + * The hash key is bound in LONG_MAX to be used for Xarray key. + */ +#define MULTIPLIER 37 +static unsigned long get_ctl_id_hash(const struct snd_ctl_elem_id *id) +{ + int i; + unsigned long h; + + h = id->iface; + h = MULTIPLIER * h + id->device; + h = MULTIPLIER * h + id->subdevice; + for (i = 0; i < SNDRV_CTL_ELEM_ID_NAME_MAXLEN && id->name[i]; i++) + h = MULTIPLIER * h + id->name[i]; + h = MULTIPLIER * h + id->index; + h &= LONG_MAX; + return h; +} + +/* add hash entries to numid and ctl xarray tables */ +static void add_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ + struct snd_ctl_elem_id id = kcontrol->id; + int i; + + xa_store_range(&card->ctl_numids, kcontrol->id.numid, + kcontrol->id.numid + kcontrol->count - 1, + kcontrol, GFP_KERNEL); + + for (i = 0; i < kcontrol->count; i++) { + id.index = kcontrol->id.index + i; + if (xa_insert(&card->ctl_hash, get_ctl_id_hash(&id), + kcontrol, GFP_KERNEL)) { + /* skip hash for this entry, noting we had collision */ + card->ctl_hash_collision = true; + dev_dbg(card->dev, "ctl_hash collision %d:%s:%d\n", + id.iface, id.name, id.index); + } + } +} + +/* remove hash entries that have been added */ +static void remove_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ + struct snd_ctl_elem_id id = kcontrol->id; + struct snd_kcontrol *matched; + unsigned long h; + int i; + + for (i = 0; i < kcontrol->count; i++) { + xa_erase(&card->ctl_numids, id.numid); + h = get_ctl_id_hash(&id); + matched = xa_load(&card->ctl_hash, h); + if (matched && (matched == kcontrol || + elem_id_matches(matched, &id))) + xa_erase(&card->ctl_hash, h); + id.index++; + id.numid++; + } +} +#else /* CONFIG_SND_CTL_FAST_LOOKUP */ +static inline void add_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ +} +static inline void remove_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ +} +#endif /* CONFIG_SND_CTL_FAST_LOOKUP */ + +enum snd_ctl_add_mode { + CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE, +}; + +/* add/replace a new kcontrol object; call with card->controls_rwsem locked */ +static int __snd_ctl_add_replace(struct snd_card *card, + struct snd_kcontrol *kcontrol, + enum snd_ctl_add_mode mode) +{ + struct snd_ctl_elem_id id; + unsigned int idx; + struct snd_kcontrol *old; + int err; + + lockdep_assert_held_write(&card->controls_rwsem); + + id = kcontrol->id; + if (id.index > UINT_MAX - kcontrol->count) + return -EINVAL; + + old = snd_ctl_find_id_locked(card, &id); + if (!old) { + if (mode == CTL_REPLACE) + return -EINVAL; + } else { + if (mode == CTL_ADD_EXCLUSIVE) { + dev_err(card->dev, + "control %i:%i:%i:%s:%i is already present\n", + id.iface, id.device, id.subdevice, id.name, + id.index); + return -EBUSY; + } + + err = snd_ctl_remove_locked(card, old); + if (err < 0) + return err; + } + + if (snd_ctl_find_hole(card, kcontrol->count) < 0) + return -ENOMEM; + + list_add_tail(&kcontrol->list, &card->controls); + card->controls_count += kcontrol->count; + kcontrol->id.numid = card->last_numid + 1; + card->last_numid += kcontrol->count; + + add_hash_entries(card, kcontrol); + + for (idx = 0; idx < kcontrol->count; idx++) + snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx); + + return 0; +} + +static int snd_ctl_add_replace(struct snd_card *card, + struct snd_kcontrol *kcontrol, + enum snd_ctl_add_mode mode) +{ + int err = -EINVAL; + + if (! kcontrol) + return err; + if (snd_BUG_ON(!card || !kcontrol->info)) + goto error; + + down_write(&card->controls_rwsem); + err = __snd_ctl_add_replace(card, kcontrol, mode); + up_write(&card->controls_rwsem); + if (err < 0) + goto error; + return 0; + + error: + snd_ctl_free_one(kcontrol); + return err; +} + +/** + * snd_ctl_add - add the control instance to the card + * @card: the card instance + * @kcontrol: the control instance to add + * + * Adds the control instance created via snd_ctl_new() or + * snd_ctl_new1() to the given card. Assigns also an unique + * numid used for fast search. + * + * It frees automatically the control which cannot be added. + * + * Return: Zero if successful, or a negative error code on failure. + * + */ +int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) +{ + return snd_ctl_add_replace(card, kcontrol, CTL_ADD_EXCLUSIVE); +} +EXPORT_SYMBOL(snd_ctl_add); + +/** + * snd_ctl_replace - replace the control instance of the card + * @card: the card instance + * @kcontrol: the control instance to replace + * @add_on_replace: add the control if not already added + * + * Replaces the given control. If the given control does not exist + * and the add_on_replace flag is set, the control is added. If the + * control exists, it is destroyed first. + * + * It frees automatically the control which cannot be added or replaced. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, + bool add_on_replace) +{ + return snd_ctl_add_replace(card, kcontrol, + add_on_replace ? CTL_ADD_ON_REPLACE : CTL_REPLACE); +} +EXPORT_SYMBOL(snd_ctl_replace); + +static int __snd_ctl_remove(struct snd_card *card, + struct snd_kcontrol *kcontrol, + bool remove_hash) +{ + unsigned int idx; + + lockdep_assert_held_write(&card->controls_rwsem); + + if (snd_BUG_ON(!card || !kcontrol)) + return -EINVAL; + list_del(&kcontrol->list); + + if (remove_hash) + remove_hash_entries(card, kcontrol); + + card->controls_count -= kcontrol->count; + for (idx = 0; idx < kcontrol->count; idx++) + snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx); + snd_ctl_free_one(kcontrol); + return 0; +} + +static inline int snd_ctl_remove_locked(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ + return __snd_ctl_remove(card, kcontrol, true); +} + +/** + * snd_ctl_remove - remove the control from the card and release it + * @card: the card instance + * @kcontrol: the control instance to remove + * + * Removes the control from the card and then releases the instance. + * You don't need to call snd_ctl_free_one(). + * + * Return: 0 if successful, or a negative error code on failure. + * + * Note that this function takes card->controls_rwsem lock internally. + */ +int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol) +{ + int ret; + + down_write(&card->controls_rwsem); + ret = snd_ctl_remove_locked(card, kcontrol); + up_write(&card->controls_rwsem); + return ret; +} +EXPORT_SYMBOL(snd_ctl_remove); + +/** + * snd_ctl_remove_id - remove the control of the given id and release it + * @card: the card instance + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Return: 0 if successful, or a negative error code on failure. + */ +int snd_ctl_remove_id(struct snd_card *card, struct snd_ctl_elem_id *id) +{ + struct snd_kcontrol *kctl; + int ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + ret = snd_ctl_remove_locked(card, kctl); + up_write(&card->controls_rwsem); + return ret; +} +EXPORT_SYMBOL(snd_ctl_remove_id); + +/** + * snd_ctl_remove_user_ctl - remove and release the unlocked user control + * @file: active control handle + * @id: the control id to remove + * + * Finds the control instance with the given id, removes it from the + * card list and releases it. + * + * Return: 0 if successful, or a negative error code on failure. + */ +static int snd_ctl_remove_user_ctl(struct snd_ctl_file * file, + struct snd_ctl_elem_id *id) +{ + struct snd_card *card = file->card; + struct snd_kcontrol *kctl; + int idx, ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, id); + if (kctl == NULL) { + ret = -ENOENT; + goto error; + } + if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_USER)) { + ret = -EINVAL; + goto error; + } + for (idx = 0; idx < kctl->count; idx++) + if (kctl->vd[idx].owner != NULL && kctl->vd[idx].owner != file) { + ret = -EBUSY; + goto error; + } + ret = snd_ctl_remove_locked(card, kctl); +error: + up_write(&card->controls_rwsem); + return ret; +} + +/** + * snd_ctl_activate_id - activate/inactivate the control of the given id + * @card: the card instance + * @id: the control id to activate/inactivate + * @active: non-zero to activate + * + * Finds the control instance with the given id, and activate or + * inactivate the control together with notification, if changed. + * The given ID data is filled with full information. + * + * Return: 0 if unchanged, 1 if changed, or a negative error code on failure. + */ +int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id, + int active) +{ + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + int ret; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, id); + if (kctl == NULL) { + ret = -ENOENT; + goto unlock; + } + index_offset = snd_ctl_get_ioff(kctl, id); + vd = &kctl->vd[index_offset]; + ret = 0; + if (active) { + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)) + goto unlock; + vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } else { + if (vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) + goto unlock; + vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } + snd_ctl_build_ioff(id, kctl, index_offset); + downgrade_write(&card->controls_rwsem); + snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, index_offset); + up_read(&card->controls_rwsem); + return 1; + + unlock: + up_write(&card->controls_rwsem); + return ret; +} +EXPORT_SYMBOL_GPL(snd_ctl_activate_id); + +/** + * snd_ctl_rename_id - replace the id of a control on the card + * @card: the card instance + * @src_id: the old id + * @dst_id: the new id + * + * Finds the control with the old id from the card, and replaces the + * id with the new one. + * + * The function tries to keep the already assigned numid while replacing + * the rest. + * + * Note that this function should be used only in the card initialization + * phase. Calling after the card instantiation may cause issues with + * user-space expecting persistent numids. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, + struct snd_ctl_elem_id *dst_id) +{ + struct snd_kcontrol *kctl; + int saved_numid; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, src_id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + saved_numid = kctl->id.numid; + remove_hash_entries(card, kctl); + kctl->id = *dst_id; + kctl->id.numid = saved_numid; + add_hash_entries(card, kctl); + up_write(&card->controls_rwsem); + return 0; +} +EXPORT_SYMBOL(snd_ctl_rename_id); + +/** + * snd_ctl_rename - rename the control on the card + * @card: the card instance + * @kctl: the control to rename + * @name: the new name + * + * Renames the specified control on the card to the new name. + * + * Note that this function takes card->controls_rwsem lock internally. + */ +void snd_ctl_rename(struct snd_card *card, struct snd_kcontrol *kctl, + const char *name) +{ + down_write(&card->controls_rwsem); + remove_hash_entries(card, kctl); + + if (strscpy(kctl->id.name, name, sizeof(kctl->id.name)) < 0) + pr_warn("ALSA: Renamed control new name '%s' truncated to '%s'\n", + name, kctl->id.name); + + add_hash_entries(card, kctl); + up_write(&card->controls_rwsem); +} +EXPORT_SYMBOL(snd_ctl_rename); + +#ifndef CONFIG_SND_CTL_FAST_LOOKUP +static struct snd_kcontrol * +snd_ctl_find_numid_slow(struct snd_card *card, unsigned int numid) +{ + struct snd_kcontrol *kctl; + + list_for_each_entry(kctl, &card->controls, list) { + if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid) + return kctl; + } + return NULL; +} +#endif /* !CONFIG_SND_CTL_FAST_LOOKUP */ + +/** + * snd_ctl_find_numid_locked - find the control instance with the given number-id + * @card: the card instance + * @numid: the number-id to search + * + * Finds the control instance with the given number-id from the card. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + * + * Return: The pointer of the instance if found, or %NULL if not. + */ +struct snd_kcontrol * +snd_ctl_find_numid_locked(struct snd_card *card, unsigned int numid) +{ + if (snd_BUG_ON(!card || !numid)) + return NULL; + lockdep_assert_held(&card->controls_rwsem); +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + return xa_load(&card->ctl_numids, numid); +#else + return snd_ctl_find_numid_slow(card, numid); +#endif +} +EXPORT_SYMBOL(snd_ctl_find_numid_locked); + +/** + * snd_ctl_find_numid - find the control instance with the given number-id + * @card: the card instance + * @numid: the number-id to search + * + * Finds the control instance with the given number-id from the card. + * + * Return: The pointer of the instance if found, or %NULL if not. + * + * Note that this function takes card->controls_rwsem lock internally. + */ +struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, + unsigned int numid) +{ + struct snd_kcontrol *kctl; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_numid_locked(card, numid); + up_read(&card->controls_rwsem); + return kctl; +} +EXPORT_SYMBOL(snd_ctl_find_numid); + +/** + * snd_ctl_find_id_locked - find the control instance with the given id + * @card: the card instance + * @id: the id to search + * + * Finds the control instance with the given id from the card. + * + * The caller must down card->controls_rwsem before calling this function + * (if the race condition can happen). + * + * Return: The pointer of the instance if found, or %NULL if not. + */ +struct snd_kcontrol *snd_ctl_find_id_locked(struct snd_card *card, + const struct snd_ctl_elem_id *id) +{ + struct snd_kcontrol *kctl; + + if (snd_BUG_ON(!card || !id)) + return NULL; + lockdep_assert_held(&card->controls_rwsem); + if (id->numid != 0) + return snd_ctl_find_numid_locked(card, id->numid); +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + kctl = xa_load(&card->ctl_hash, get_ctl_id_hash(id)); + if (kctl && elem_id_matches(kctl, id)) + return kctl; + if (!card->ctl_hash_collision) + return NULL; /* we can rely on only hash table */ +#endif + /* no matching in hash table - try all as the last resort */ + list_for_each_entry(kctl, &card->controls, list) + if (elem_id_matches(kctl, id)) + return kctl; + + return NULL; +} +EXPORT_SYMBOL(snd_ctl_find_id_locked); + +/** + * snd_ctl_find_id - find the control instance with the given id + * @card: the card instance + * @id: the id to search + * + * Finds the control instance with the given id from the card. + * + * Return: The pointer of the instance if found, or %NULL if not. + * + * Note that this function takes card->controls_rwsem lock internally. + */ +struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card, + const struct snd_ctl_elem_id *id) +{ + struct snd_kcontrol *kctl; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, id); + up_read(&card->controls_rwsem); + return kctl; +} +EXPORT_SYMBOL(snd_ctl_find_id); + +static int snd_ctl_card_info(struct snd_card *card, struct snd_ctl_file * ctl, + unsigned int cmd, void __user *arg) +{ + struct snd_ctl_card_info *info; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + down_read(&snd_ioctl_rwsem); + info->card = card->number; + strscpy(info->id, card->id, sizeof(info->id)); + strscpy(info->driver, card->driver, sizeof(info->driver)); + strscpy(info->name, card->shortname, sizeof(info->name)); + strscpy(info->longname, card->longname, sizeof(info->longname)); + strscpy(info->mixername, card->mixername, sizeof(info->mixername)); + strscpy(info->components, card->components, sizeof(info->components)); + up_read(&snd_ioctl_rwsem); + if (copy_to_user(arg, info, sizeof(struct snd_ctl_card_info))) { + kfree(info); + return -EFAULT; + } + kfree(info); + return 0; +} + +static int snd_ctl_elem_list(struct snd_card *card, + struct snd_ctl_elem_list *list) +{ + struct snd_kcontrol *kctl; + struct snd_ctl_elem_id id; + unsigned int offset, space, jidx; + int err = 0; + + offset = list->offset; + space = list->space; + + down_read(&card->controls_rwsem); + list->count = card->controls_count; + list->used = 0; + if (space > 0) { + list_for_each_entry(kctl, &card->controls, list) { + if (offset >= kctl->count) { + offset -= kctl->count; + continue; + } + for (jidx = offset; jidx < kctl->count; jidx++) { + snd_ctl_build_ioff(&id, kctl, jidx); + if (copy_to_user(list->pids + list->used, &id, + sizeof(id))) { + err = -EFAULT; + goto out; + } + list->used++; + if (!--space) + goto out; + } + offset = 0; + } + } + out: + up_read(&card->controls_rwsem); + return err; +} + +static int snd_ctl_elem_list_user(struct snd_card *card, + struct snd_ctl_elem_list __user *_list) +{ + struct snd_ctl_elem_list list; + int err; + + if (copy_from_user(&list, _list, sizeof(list))) + return -EFAULT; + err = snd_ctl_elem_list(card, &list); + if (err) + return err; + if (copy_to_user(_list, &list, sizeof(list))) + return -EFAULT; + + return 0; +} + +/* Check whether the given kctl info is valid */ +static int snd_ctl_check_elem_info(struct snd_card *card, + const struct snd_ctl_elem_info *info) +{ + static const unsigned int max_value_counts[] = { + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = 128, + [SNDRV_CTL_ELEM_TYPE_INTEGER] = 128, + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = 128, + [SNDRV_CTL_ELEM_TYPE_BYTES] = 512, + [SNDRV_CTL_ELEM_TYPE_IEC958] = 1, + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = 64, + }; + + if (info->type < SNDRV_CTL_ELEM_TYPE_BOOLEAN || + info->type > SNDRV_CTL_ELEM_TYPE_INTEGER64) { + if (card) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: invalid type %d\n", + info->id.iface, info->id.device, + info->id.subdevice, info->id.name, + info->id.index, info->type); + return -EINVAL; + } + if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED && + info->value.enumerated.items == 0) { + if (card) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: zero enum items\n", + info->id.iface, info->id.device, + info->id.subdevice, info->id.name, + info->id.index); + return -EINVAL; + } + if (info->count > max_value_counts[info->type]) { + if (card) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: invalid count %d\n", + info->id.iface, info->id.device, + info->id.subdevice, info->id.name, + info->id.index, info->count); + return -EINVAL; + } + + return 0; +} + +/* The capacity of struct snd_ctl_elem_value.value.*/ +static const unsigned int value_sizes[] = { + [SNDRV_CTL_ELEM_TYPE_BOOLEAN] = sizeof(long), + [SNDRV_CTL_ELEM_TYPE_INTEGER] = sizeof(long), + [SNDRV_CTL_ELEM_TYPE_ENUMERATED] = sizeof(unsigned int), + [SNDRV_CTL_ELEM_TYPE_BYTES] = sizeof(unsigned char), + [SNDRV_CTL_ELEM_TYPE_IEC958] = sizeof(struct snd_aes_iec958), + [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), +}; + +/* fill the remaining snd_ctl_elem_value data with the given pattern */ +static void fill_remaining_elem_value(struct snd_ctl_elem_value *control, + struct snd_ctl_elem_info *info, + u32 pattern) +{ + size_t offset = value_sizes[info->type] * info->count; + + offset = DIV_ROUND_UP(offset, sizeof(u32)); + memset32((u32 *)control->value.bytes.data + offset, pattern, + sizeof(control->value) / sizeof(u32) - offset); +} + +/* check whether the given integer ctl value is valid */ +static int sanity_check_int_value(struct snd_card *card, + const struct snd_ctl_elem_value *control, + const struct snd_ctl_elem_info *info, + int i, bool print_error) +{ + long long lval, lmin, lmax, lstep; + u64 rem; + + switch (info->type) { + default: + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + lval = control->value.integer.value[i]; + lmin = 0; + lmax = 1; + lstep = 0; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + lval = control->value.integer.value[i]; + lmin = info->value.integer.min; + lmax = info->value.integer.max; + lstep = info->value.integer.step; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + lval = control->value.integer64.value[i]; + lmin = info->value.integer64.min; + lmax = info->value.integer64.max; + lstep = info->value.integer64.step; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + lval = control->value.enumerated.item[i]; + lmin = 0; + lmax = info->value.enumerated.items - 1; + lstep = 0; + break; + } + + if (lval < lmin || lval > lmax) { + if (print_error) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: value out of range %lld (%lld/%lld) at count %i\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index, lval, lmin, lmax, i); + return -EINVAL; + } + if (lstep) { + div64_u64_rem(lval, lstep, &rem); + if (rem) { + if (print_error) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: unaligned value %lld (step %lld) at count %i\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index, lval, lstep, i); + return -EINVAL; + } + } + + return 0; +} + +/* check whether the all input values are valid for the given elem value */ +static int sanity_check_input_values(struct snd_card *card, + const struct snd_ctl_elem_value *control, + const struct snd_ctl_elem_info *info, + bool print_error) +{ + int i, ret; + + switch (info->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < info->count; i++) { + ret = sanity_check_int_value(card, control, info, i, + print_error); + if (ret < 0) + return ret; + } + break; + default: + break; + } + + return 0; +} + +/* perform sanity checks to the given snd_ctl_elem_value object */ +static int sanity_check_elem_value(struct snd_card *card, + const struct snd_ctl_elem_value *control, + const struct snd_ctl_elem_info *info, + u32 pattern) +{ + size_t offset; + int ret; + u32 *p; + + ret = sanity_check_input_values(card, control, info, true); + if (ret < 0) + return ret; + + /* check whether the remaining area kept untouched */ + offset = value_sizes[info->type] * info->count; + offset = DIV_ROUND_UP(offset, sizeof(u32)); + p = (u32 *)control->value.bytes.data + offset; + for (; offset < sizeof(control->value) / sizeof(u32); offset++, p++) { + if (*p != pattern) { + ret = -EINVAL; + break; + } + *p = 0; /* clear the checked area */ + } + + return ret; +} + +static int __snd_ctl_elem_info(struct snd_card *card, + struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *info, + struct snd_ctl_file *ctl) +{ + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + int result; + +#ifdef CONFIG_SND_DEBUG + info->access = 0; +#endif + result = snd_power_ref_and_wait(card); + if (!result) + result = kctl->info(kctl, info); + snd_power_unref(card); + if (result >= 0) { + snd_BUG_ON(info->access); + index_offset = snd_ctl_get_ioff(kctl, &info->id); + vd = &kctl->vd[index_offset]; + snd_ctl_build_ioff(&info->id, kctl, index_offset); + info->access = vd->access; + if (vd->owner) { + info->access |= SNDRV_CTL_ELEM_ACCESS_LOCK; + if (vd->owner == ctl) + info->access |= SNDRV_CTL_ELEM_ACCESS_OWNER; + info->owner = pid_vnr(vd->owner->pid); + } else { + info->owner = -1; + } + if (!snd_ctl_skip_validation(info) && + snd_ctl_check_elem_info(card, info) < 0) + result = -EINVAL; + } + return result; +} + +static int snd_ctl_elem_info(struct snd_ctl_file *ctl, + struct snd_ctl_elem_info *info) +{ + struct snd_card *card = ctl->card; + struct snd_kcontrol *kctl; + int result; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, &info->id); + if (kctl == NULL) + result = -ENOENT; + else + result = __snd_ctl_elem_info(card, kctl, info, ctl); + up_read(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl, + struct snd_ctl_elem_info __user *_info) +{ + struct snd_ctl_elem_info info; + int result; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + result = snd_ctl_elem_info(ctl, &info); + if (result < 0) + return result; + /* drop internal access flags */ + info.access &= ~(SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK| + SNDRV_CTL_ELEM_ACCESS_LED_MASK); + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return result; +} + +static int snd_ctl_elem_read(struct snd_card *card, + struct snd_ctl_elem_value *control) +{ + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + struct snd_ctl_elem_info info; + const u32 pattern = 0xdeadbeef; + int ret; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, &control->id); + if (kctl == NULL) { + ret = -ENOENT; + goto unlock; + } + + index_offset = snd_ctl_get_ioff(kctl, &control->id); + vd = &kctl->vd[index_offset]; + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_READ) || kctl->get == NULL) { + ret = -EPERM; + goto unlock; + } + + snd_ctl_build_ioff(&control->id, kctl, index_offset); + +#ifdef CONFIG_SND_CTL_DEBUG + /* info is needed only for validation */ + memset(&info, 0, sizeof(info)); + info.id = control->id; + ret = __snd_ctl_elem_info(card, kctl, &info, NULL); + if (ret < 0) + goto unlock; +#endif + + if (!snd_ctl_skip_validation(&info)) + fill_remaining_elem_value(control, &info, pattern); + ret = snd_power_ref_and_wait(card); + if (!ret) + ret = kctl->get(kctl, control); + snd_power_unref(card); + if (ret < 0) + goto unlock; + if (!snd_ctl_skip_validation(&info) && + sanity_check_elem_value(card, control, &info, pattern) < 0) { + dev_err(card->dev, + "control %i:%i:%i:%s:%i: access overflow\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index); + ret = -EINVAL; + goto unlock; + } +unlock: + up_read(&card->controls_rwsem); + return ret; +} + +static int snd_ctl_elem_read_user(struct snd_card *card, + struct snd_ctl_elem_value __user *_control) +{ + struct snd_ctl_elem_value *control; + int result; + + control = memdup_user(_control, sizeof(*control)); + if (IS_ERR(control)) + return PTR_ERR(control); + + result = snd_ctl_elem_read(card, control); + if (result < 0) + goto error; + + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + error: + kfree(control); + return result; +} + +static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, + struct snd_ctl_elem_value *control) +{ + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int index_offset; + int result; + + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, &control->id); + if (kctl == NULL) { + up_write(&card->controls_rwsem); + return -ENOENT; + } + + index_offset = snd_ctl_get_ioff(kctl, &control->id); + vd = &kctl->vd[index_offset]; + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_WRITE) || kctl->put == NULL || + (file && vd->owner && vd->owner != file)) { + up_write(&card->controls_rwsem); + return -EPERM; + } + + snd_ctl_build_ioff(&control->id, kctl, index_offset); + result = snd_power_ref_and_wait(card); + /* validate input values */ + if (IS_ENABLED(CONFIG_SND_CTL_INPUT_VALIDATION) && !result) { + struct snd_ctl_elem_info info; + + memset(&info, 0, sizeof(info)); + info.id = control->id; + result = __snd_ctl_elem_info(card, kctl, &info, NULL); + if (!result) + result = sanity_check_input_values(card, control, &info, + false); + } + if (!result) + result = kctl->put(kctl, control); + snd_power_unref(card); + if (result < 0) { + up_write(&card->controls_rwsem); + return result; + } + + if (result > 0) { + downgrade_write(&card->controls_rwsem); + snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, index_offset); + up_read(&card->controls_rwsem); + } else { + up_write(&card->controls_rwsem); + } + + return 0; +} + +static int snd_ctl_elem_write_user(struct snd_ctl_file *file, + struct snd_ctl_elem_value __user *_control) +{ + struct snd_ctl_elem_value *control; + struct snd_card *card; + int result; + + control = memdup_user(_control, sizeof(*control)); + if (IS_ERR(control)) + return PTR_ERR(control); + + card = file->card; + result = snd_ctl_elem_write(card, file, control); + if (result < 0) + goto error; + + if (copy_to_user(_control, control, sizeof(*control))) + result = -EFAULT; + error: + kfree(control); + return result; +} + +static int snd_ctl_elem_lock(struct snd_ctl_file *file, + struct snd_ctl_elem_id __user *_id) +{ + struct snd_card *card = file->card; + struct snd_ctl_elem_id id; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner != NULL) + result = -EBUSY; + else { + vd->owner = file; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +static int snd_ctl_elem_unlock(struct snd_ctl_file *file, + struct snd_ctl_elem_id __user *_id) +{ + struct snd_card *card = file->card; + struct snd_ctl_elem_id id; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + int result; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, &id); + if (kctl == NULL) { + result = -ENOENT; + } else { + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + if (vd->owner == NULL) + result = -EINVAL; + else if (vd->owner != file) + result = -EPERM; + else { + vd->owner = NULL; + result = 0; + } + } + up_write(&card->controls_rwsem); + return result; +} + +struct user_element { + struct snd_ctl_elem_info info; + struct snd_card *card; + char *elem_data; /* element data */ + unsigned long elem_data_size; /* size of element data in bytes */ + void *tlv_data; /* TLV data */ + unsigned long tlv_data_size; /* TLV data size */ + void *priv_data; /* private data (like strings for enumerated type) */ +}; + +// check whether the addition (in bytes) of user ctl element may overflow the limit. +static bool check_user_elem_overflow(struct snd_card *card, ssize_t add) +{ + return (ssize_t)card->user_ctl_alloc_size + add > max_user_ctl_alloc_size; +} + +static int snd_ctl_elem_user_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct user_element *ue = kcontrol->private_data; + unsigned int offset; + + offset = snd_ctl_get_ioff(kcontrol, &uinfo->id); + *uinfo = ue->info; + snd_ctl_build_ioff(&uinfo->id, kcontrol, offset); + + return 0; +} + +static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct user_element *ue = kcontrol->private_data; + const char *names; + unsigned int item; + unsigned int offset; + + item = uinfo->value.enumerated.item; + + offset = snd_ctl_get_ioff(kcontrol, &uinfo->id); + *uinfo = ue->info; + snd_ctl_build_ioff(&uinfo->id, kcontrol, offset); + + item = min(item, uinfo->value.enumerated.items - 1); + uinfo->value.enumerated.item = item; + + names = ue->priv_data; + for (; item > 0; --item) + names += strlen(names) + 1; + strcpy(uinfo->value.enumerated.name, names); + + return 0; +} + +static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct user_element *ue = kcontrol->private_data; + unsigned int size = ue->elem_data_size; + char *src = ue->elem_data + + snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; + + memcpy(&ucontrol->value, src, size); + return 0; +} + +static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + struct user_element *ue = kcontrol->private_data; + unsigned int size = ue->elem_data_size; + char *dst = ue->elem_data + + snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; + + change = memcmp(&ucontrol->value, dst, size) != 0; + if (change) + memcpy(dst, &ucontrol->value, size); + return change; +} + +/* called in controls_rwsem write lock */ +static int replace_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, + unsigned int size) +{ + struct user_element *ue = kctl->private_data; + unsigned int *container; + unsigned int mask = 0; + int i; + int change; + + lockdep_assert_held_write(&ue->card->controls_rwsem); + + if (size > 1024 * 128) /* sane value */ + return -EINVAL; + + // does the TLV size change cause overflow? + if (check_user_elem_overflow(ue->card, (ssize_t)(size - ue->tlv_data_size))) + return -ENOMEM; + + container = vmemdup_user(buf, size); + if (IS_ERR(container)) + return PTR_ERR(container); + + change = ue->tlv_data_size != size; + if (!change) + change = memcmp(ue->tlv_data, container, size) != 0; + if (!change) { + kvfree(container); + return 0; + } + + if (ue->tlv_data == NULL) { + /* Now TLV data is available. */ + for (i = 0; i < kctl->count; ++i) + kctl->vd[i].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + mask = SNDRV_CTL_EVENT_MASK_INFO; + } else { + ue->card->user_ctl_alloc_size -= ue->tlv_data_size; + ue->tlv_data_size = 0; + kvfree(ue->tlv_data); + } + + ue->tlv_data = container; + ue->tlv_data_size = size; + // decremented at private_free. + ue->card->user_ctl_alloc_size += size; + + mask |= SNDRV_CTL_EVENT_MASK_TLV; + for (i = 0; i < kctl->count; ++i) + snd_ctl_notify_one(ue->card, mask, kctl, i); + + return change; +} + +static int read_user_tlv(struct snd_kcontrol *kctl, unsigned int __user *buf, + unsigned int size) +{ + struct user_element *ue = kctl->private_data; + + if (ue->tlv_data_size == 0 || ue->tlv_data == NULL) + return -ENXIO; + + if (size < ue->tlv_data_size) + return -ENOSPC; + + if (copy_to_user(buf, ue->tlv_data, ue->tlv_data_size)) + return -EFAULT; + + return 0; +} + +static int snd_ctl_elem_user_tlv(struct snd_kcontrol *kctl, int op_flag, + unsigned int size, unsigned int __user *buf) +{ + if (op_flag == SNDRV_CTL_TLV_OP_WRITE) + return replace_user_tlv(kctl, buf, size); + else + return read_user_tlv(kctl, buf, size); +} + +/* called in controls_rwsem write lock */ +static int snd_ctl_elem_init_enum_names(struct user_element *ue) +{ + char *names, *p; + size_t buf_len, name_len; + unsigned int i; + const uintptr_t user_ptrval = ue->info.value.enumerated.names_ptr; + + lockdep_assert_held_write(&ue->card->controls_rwsem); + + buf_len = ue->info.value.enumerated.names_length; + if (buf_len > 64 * 1024) + return -EINVAL; + + if (check_user_elem_overflow(ue->card, buf_len)) + return -ENOMEM; + names = vmemdup_user((const void __user *)user_ptrval, buf_len); + if (IS_ERR(names)) + return PTR_ERR(names); + + /* check that there are enough valid names */ + p = names; + for (i = 0; i < ue->info.value.enumerated.items; ++i) { + name_len = strnlen(p, buf_len); + if (name_len == 0 || name_len >= 64 || name_len == buf_len) { + kvfree(names); + return -EINVAL; + } + p += name_len + 1; + buf_len -= name_len + 1; + } + + ue->priv_data = names; + ue->info.value.enumerated.names_ptr = 0; + // increment the allocation size; decremented again at private_free. + ue->card->user_ctl_alloc_size += ue->info.value.enumerated.names_length; + + return 0; +} + +static size_t compute_user_elem_size(size_t size, unsigned int count) +{ + return sizeof(struct user_element) + size * count; +} + +static void snd_ctl_elem_user_free(struct snd_kcontrol *kcontrol) +{ + struct user_element *ue = kcontrol->private_data; + + // decrement the allocation size. + ue->card->user_ctl_alloc_size -= compute_user_elem_size(ue->elem_data_size, kcontrol->count); + ue->card->user_ctl_alloc_size -= ue->tlv_data_size; + if (ue->priv_data) + ue->card->user_ctl_alloc_size -= ue->info.value.enumerated.names_length; + + kvfree(ue->tlv_data); + kvfree(ue->priv_data); + kfree(ue); +} + +static int snd_ctl_elem_add(struct snd_ctl_file *file, + struct snd_ctl_elem_info *info, int replace) +{ + struct snd_card *card = file->card; + struct snd_kcontrol *kctl; + unsigned int count; + unsigned int access; + long private_size; + size_t alloc_size; + struct user_element *ue; + unsigned int offset; + int err; + + if (!*info->id.name) + return -EINVAL; + if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name)) + return -EINVAL; + + /* Delete a control to replace them if needed. */ + if (replace) { + info->id.numid = 0; + err = snd_ctl_remove_user_ctl(file, &info->id); + if (err) + return err; + } + + /* Check the number of elements for this userspace control. */ + count = info->owner; + if (count == 0) + count = 1; + + /* Arrange access permissions if needed. */ + access = info->access; + if (access == 0) + access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_INACTIVE | + SNDRV_CTL_ELEM_ACCESS_TLV_WRITE); + + /* In initial state, nothing is available as TLV container. */ + if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) + access |= SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + access |= SNDRV_CTL_ELEM_ACCESS_USER; + + /* + * Check information and calculate the size of data specific to + * this userspace control. + */ + /* pass NULL to card for suppressing error messages */ + err = snd_ctl_check_elem_info(NULL, info); + if (err < 0) + return err; + /* user-space control doesn't allow zero-size data */ + if (info->count < 1) + return -EINVAL; + private_size = value_sizes[info->type] * info->count; + alloc_size = compute_user_elem_size(private_size, count); + + down_write(&card->controls_rwsem); + if (check_user_elem_overflow(card, alloc_size)) { + err = -ENOMEM; + goto unlock; + } + + /* + * Keep memory object for this userspace control. After passing this + * code block, the instance should be freed by snd_ctl_free_one(). + * + * Note that these elements in this control are locked. + */ + err = snd_ctl_new(&kctl, count, access, file); + if (err < 0) + goto unlock; + memcpy(&kctl->id, &info->id, sizeof(kctl->id)); + ue = kzalloc(alloc_size, GFP_KERNEL); + if (!ue) { + kfree(kctl); + err = -ENOMEM; + goto unlock; + } + kctl->private_data = ue; + kctl->private_free = snd_ctl_elem_user_free; + + // increment the allocated size; decremented again at private_free. + card->user_ctl_alloc_size += alloc_size; + + /* Set private data for this userspace control. */ + ue->card = card; + ue->info = *info; + ue->info.access = 0; + ue->elem_data = (char *)ue + sizeof(*ue); + ue->elem_data_size = private_size; + if (ue->info.type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) { + err = snd_ctl_elem_init_enum_names(ue); + if (err < 0) { + snd_ctl_free_one(kctl); + goto unlock; + } + } + + /* Set callback functions. */ + if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) + kctl->info = snd_ctl_elem_user_enum_info; + else + kctl->info = snd_ctl_elem_user_info; + if (access & SNDRV_CTL_ELEM_ACCESS_READ) + kctl->get = snd_ctl_elem_user_get; + if (access & SNDRV_CTL_ELEM_ACCESS_WRITE) + kctl->put = snd_ctl_elem_user_put; + if (access & SNDRV_CTL_ELEM_ACCESS_TLV_WRITE) + kctl->tlv.c = snd_ctl_elem_user_tlv; + + /* This function manage to free the instance on failure. */ + err = __snd_ctl_add_replace(card, kctl, CTL_ADD_EXCLUSIVE); + if (err < 0) { + snd_ctl_free_one(kctl); + goto unlock; + } + offset = snd_ctl_get_ioff(kctl, &info->id); + snd_ctl_build_ioff(&info->id, kctl, offset); + /* + * Here we cannot fill any field for the number of elements added by + * this operation because there're no specific fields. The usage of + * 'owner' field for this purpose may cause any bugs to userspace + * applications because the field originally means PID of a process + * which locks the element. + */ + unlock: + up_write(&card->controls_rwsem); + return err; +} + +static int snd_ctl_elem_add_user(struct snd_ctl_file *file, + struct snd_ctl_elem_info __user *_info, int replace) +{ + struct snd_ctl_elem_info info; + int err; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + err = snd_ctl_elem_add(file, &info, replace); + if (err < 0) + return err; + if (copy_to_user(_info, &info, sizeof(info))) { + snd_ctl_remove_user_ctl(file, &info.id); + return -EFAULT; + } + + return 0; +} + +static int snd_ctl_elem_remove(struct snd_ctl_file *file, + struct snd_ctl_elem_id __user *_id) +{ + struct snd_ctl_elem_id id; + + if (copy_from_user(&id, _id, sizeof(id))) + return -EFAULT; + return snd_ctl_remove_user_ctl(file, &id); +} + +static int snd_ctl_subscribe_events(struct snd_ctl_file *file, int __user *ptr) +{ + int subscribe; + if (get_user(subscribe, ptr)) + return -EFAULT; + if (subscribe < 0) { + subscribe = file->subscribed; + if (put_user(subscribe, ptr)) + return -EFAULT; + return 0; + } + if (subscribe) { + file->subscribed = 1; + return 0; + } else if (file->subscribed) { + snd_ctl_empty_read_queue(file); + file->subscribed = 0; + } + return 0; +} + +static int call_tlv_handler(struct snd_ctl_file *file, int op_flag, + struct snd_kcontrol *kctl, + struct snd_ctl_elem_id *id, + unsigned int __user *buf, unsigned int size) +{ + static const struct { + int op; + int perm; + } pairs[] = { + {SNDRV_CTL_TLV_OP_READ, SNDRV_CTL_ELEM_ACCESS_TLV_READ}, + {SNDRV_CTL_TLV_OP_WRITE, SNDRV_CTL_ELEM_ACCESS_TLV_WRITE}, + {SNDRV_CTL_TLV_OP_CMD, SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND}, + }; + struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; + int i, ret; + + /* Check support of the request for this element. */ + for (i = 0; i < ARRAY_SIZE(pairs); ++i) { + if (op_flag == pairs[i].op && (vd->access & pairs[i].perm)) + break; + } + if (i == ARRAY_SIZE(pairs)) + return -ENXIO; + + if (kctl->tlv.c == NULL) + return -ENXIO; + + /* Write and command operations are not allowed for locked element. */ + if (op_flag != SNDRV_CTL_TLV_OP_READ && + vd->owner != NULL && vd->owner != file) + return -EPERM; + + ret = snd_power_ref_and_wait(file->card); + if (!ret) + ret = kctl->tlv.c(kctl, op_flag, size, buf); + snd_power_unref(file->card); + return ret; +} + +static int read_tlv_buf(struct snd_kcontrol *kctl, struct snd_ctl_elem_id *id, + unsigned int __user *buf, unsigned int size) +{ + struct snd_kcontrol_volatile *vd = &kctl->vd[snd_ctl_get_ioff(kctl, id)]; + unsigned int len; + + if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_READ)) + return -ENXIO; + + if (kctl->tlv.p == NULL) + return -ENXIO; + + len = sizeof(unsigned int) * 2 + kctl->tlv.p[1]; + if (size < len) + return -ENOMEM; + + if (copy_to_user(buf, kctl->tlv.p, len)) + return -EFAULT; + + return 0; +} + +static int snd_ctl_tlv_ioctl(struct snd_ctl_file *file, + struct snd_ctl_tlv __user *buf, + int op_flag) +{ + struct snd_ctl_tlv header; + unsigned int __user *container; + unsigned int container_size; + struct snd_kcontrol *kctl; + struct snd_ctl_elem_id id; + struct snd_kcontrol_volatile *vd; + + lockdep_assert_held(&file->card->controls_rwsem); + + if (copy_from_user(&header, buf, sizeof(header))) + return -EFAULT; + + /* In design of control core, numerical ID starts at 1. */ + if (header.numid == 0) + return -EINVAL; + + /* At least, container should include type and length fields. */ + if (header.length < sizeof(unsigned int) * 2) + return -EINVAL; + container_size = header.length; + container = buf->tlv; + + kctl = snd_ctl_find_numid_locked(file->card, header.numid); + if (kctl == NULL) + return -ENOENT; + + /* Calculate index of the element in this set. */ + id = kctl->id; + snd_ctl_build_ioff(&id, kctl, header.numid - id.numid); + vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)]; + + if (vd->access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { + return call_tlv_handler(file, op_flag, kctl, &id, container, + container_size); + } else { + if (op_flag == SNDRV_CTL_TLV_OP_READ) { + return read_tlv_buf(kctl, &id, container, + container_size); + } + } + + /* Not supported. */ + return -ENXIO; +} + +static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_ctl_file *ctl; + struct snd_card *card; + struct snd_kctl_ioctl *p; + void __user *argp = (void __user *)arg; + int __user *ip = argp; + int err; + + ctl = file->private_data; + card = ctl->card; + if (snd_BUG_ON(!card)) + return -ENXIO; + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0; + case SNDRV_CTL_IOCTL_CARD_INFO: + return snd_ctl_card_info(card, ctl, cmd, argp); + case SNDRV_CTL_IOCTL_ELEM_LIST: + return snd_ctl_elem_list_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO: + return snd_ctl_elem_info_user(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_READ: + return snd_ctl_elem_read_user(card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE: + return snd_ctl_elem_write_user(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_LOCK: + return snd_ctl_elem_lock(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_UNLOCK: + return snd_ctl_elem_unlock(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_ADD: + return snd_ctl_elem_add_user(ctl, argp, 0); + case SNDRV_CTL_IOCTL_ELEM_REPLACE: + return snd_ctl_elem_add_user(ctl, argp, 1); + case SNDRV_CTL_IOCTL_ELEM_REMOVE: + return snd_ctl_elem_remove(ctl, argp); + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + return snd_ctl_subscribe_events(ctl, ip); + case SNDRV_CTL_IOCTL_TLV_READ: + down_read(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ); + up_read(&ctl->card->controls_rwsem); + return err; + case SNDRV_CTL_IOCTL_TLV_WRITE: + down_write(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE); + up_write(&ctl->card->controls_rwsem); + return err; + case SNDRV_CTL_IOCTL_TLV_COMMAND: + down_write(&ctl->card->controls_rwsem); + err = snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD); + up_write(&ctl->card->controls_rwsem); + return err; + case SNDRV_CTL_IOCTL_POWER: + return -ENOPROTOOPT; + case SNDRV_CTL_IOCTL_POWER_STATE: + return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0; + } + down_read(&snd_ioctl_rwsem); + list_for_each_entry(p, &snd_control_ioctls, list) { + err = p->fioctl(card, ctl, cmd, arg); + if (err != -ENOIOCTLCMD) { + up_read(&snd_ioctl_rwsem); + return err; + } + } + up_read(&snd_ioctl_rwsem); + dev_dbg(card->dev, "unknown ioctl = 0x%x\n", cmd); + return -ENOTTY; +} + +static ssize_t snd_ctl_read(struct file *file, char __user *buffer, + size_t count, loff_t * offset) +{ + struct snd_ctl_file *ctl; + int err = 0; + ssize_t result = 0; + + ctl = file->private_data; + if (snd_BUG_ON(!ctl || !ctl->card)) + return -ENXIO; + if (!ctl->subscribed) + return -EBADFD; + if (count < sizeof(struct snd_ctl_event)) + return -EINVAL; + spin_lock_irq(&ctl->read_lock); + while (count >= sizeof(struct snd_ctl_event)) { + struct snd_ctl_event ev; + struct snd_kctl_event *kev; + while (list_empty(&ctl->events)) { + wait_queue_entry_t wait; + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + err = -EAGAIN; + goto __end_lock; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&ctl->change_sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&ctl->read_lock); + schedule(); + remove_wait_queue(&ctl->change_sleep, &wait); + if (ctl->card->shutdown) + return -ENODEV; + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&ctl->read_lock); + } + kev = snd_kctl_event(ctl->events.next); + ev.type = SNDRV_CTL_EVENT_ELEM; + ev.data.elem.mask = kev->mask; + ev.data.elem.id = kev->id; + list_del(&kev->list); + spin_unlock_irq(&ctl->read_lock); + kfree(kev); + if (copy_to_user(buffer, &ev, sizeof(struct snd_ctl_event))) { + err = -EFAULT; + goto __end; + } + spin_lock_irq(&ctl->read_lock); + buffer += sizeof(struct snd_ctl_event); + count -= sizeof(struct snd_ctl_event); + result += sizeof(struct snd_ctl_event); + } + __end_lock: + spin_unlock_irq(&ctl->read_lock); + __end: + return result > 0 ? result : err; +} + +static __poll_t snd_ctl_poll(struct file *file, poll_table * wait) +{ + __poll_t mask; + struct snd_ctl_file *ctl; + + ctl = file->private_data; + if (!ctl->subscribed) + return 0; + poll_wait(file, &ctl->change_sleep, wait); + + mask = 0; + if (!list_empty(&ctl->events)) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} + +/* + * register the device-specific control-ioctls. + * called from each device manager like pcm.c, hwdep.c, etc. + */ +static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head *lists) +{ + struct snd_kctl_ioctl *pn; + + pn = kzalloc(sizeof(struct snd_kctl_ioctl), GFP_KERNEL); + if (pn == NULL) + return -ENOMEM; + pn->fioctl = fcn; + down_write(&snd_ioctl_rwsem); + list_add_tail(&pn->list, lists); + up_write(&snd_ioctl_rwsem); + return 0; +} + +/** + * snd_ctl_register_ioctl - register the device-specific control-ioctls + * @fcn: ioctl callback function + * + * called from each device manager like pcm.c, hwdep.c, etc. + * + * Return: zero if successful, or a negative error code + */ +int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_ioctls); +} +EXPORT_SYMBOL(snd_ctl_register_ioctl); + +#ifdef CONFIG_COMPAT +/** + * snd_ctl_register_ioctl_compat - register the device-specific 32bit compat + * control-ioctls + * @fcn: ioctl callback function + * + * Return: zero if successful, or a negative error code + */ +int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_register_ioctl(fcn, &snd_control_compat_ioctls); +} +EXPORT_SYMBOL(snd_ctl_register_ioctl_compat); +#endif + +/* + * de-register the device-specific control-ioctls. + */ +static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, + struct list_head *lists) +{ + struct snd_kctl_ioctl *p; + + if (snd_BUG_ON(!fcn)) + return -EINVAL; + down_write(&snd_ioctl_rwsem); + list_for_each_entry(p, lists, list) { + if (p->fioctl == fcn) { + list_del(&p->list); + up_write(&snd_ioctl_rwsem); + kfree(p); + return 0; + } + } + up_write(&snd_ioctl_rwsem); + snd_BUG(); + return -EINVAL; +} + +/** + * snd_ctl_unregister_ioctl - de-register the device-specific control-ioctls + * @fcn: ioctl callback function to unregister + * + * Return: zero if successful, or a negative error code + */ +int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_ioctls); +} +EXPORT_SYMBOL(snd_ctl_unregister_ioctl); + +#ifdef CONFIG_COMPAT +/** + * snd_ctl_unregister_ioctl_compat - de-register the device-specific compat + * 32bit control-ioctls + * @fcn: ioctl callback function to unregister + * + * Return: zero if successful, or a negative error code + */ +int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn) +{ + return _snd_ctl_unregister_ioctl(fcn, &snd_control_compat_ioctls); +} +EXPORT_SYMBOL(snd_ctl_unregister_ioctl_compat); +#endif + +static int snd_ctl_fasync(int fd, struct file * file, int on) +{ + struct snd_ctl_file *ctl; + + ctl = file->private_data; + return snd_fasync_helper(fd, file, on, &ctl->fasync); +} + +/* return the preferred subdevice number if already assigned; + * otherwise return -1 + */ +int snd_ctl_get_preferred_subdevice(struct snd_card *card, int type) +{ + struct snd_ctl_file *kctl; + int subdevice = -1; + unsigned long flags; + + read_lock_irqsave(&card->ctl_files_rwlock, flags); + list_for_each_entry(kctl, &card->ctl_files, list) { + if (kctl->pid == task_pid(current)) { + subdevice = kctl->preferred_subdevice[type]; + if (subdevice != -1) + break; + } + } + read_unlock_irqrestore(&card->ctl_files_rwlock, flags); + return subdevice; +} +EXPORT_SYMBOL_GPL(snd_ctl_get_preferred_subdevice); + +/* + * ioctl32 compat + */ +#ifdef CONFIG_COMPAT +#include "control_compat.c" +#else +#define snd_ctl_ioctl_compat NULL +#endif + +/* + * control layers (audio LED etc.) + */ + +/** + * snd_ctl_request_layer - request to use the layer + * @module_name: Name of the kernel module (NULL == build-in) + * + * Return: zero if successful, or an error code when the module cannot be loaded + */ +int snd_ctl_request_layer(const char *module_name) +{ + struct snd_ctl_layer_ops *lops; + + if (module_name == NULL) + return 0; + down_read(&snd_ctl_layer_rwsem); + for (lops = snd_ctl_layer; lops; lops = lops->next) + if (strcmp(lops->module_name, module_name) == 0) + break; + up_read(&snd_ctl_layer_rwsem); + if (lops) + return 0; + return request_module(module_name); +} +EXPORT_SYMBOL_GPL(snd_ctl_request_layer); + +/** + * snd_ctl_register_layer - register new control layer + * @lops: operation structure + * + * The new layer can track all control elements and do additional + * operations on top (like audio LED handling). + */ +void snd_ctl_register_layer(struct snd_ctl_layer_ops *lops) +{ + struct snd_card *card; + int card_number; + + down_write(&snd_ctl_layer_rwsem); + lops->next = snd_ctl_layer; + snd_ctl_layer = lops; + up_write(&snd_ctl_layer_rwsem); + for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { + card = snd_card_ref(card_number); + if (card) { + down_read(&card->controls_rwsem); + lops->lregister(card); + up_read(&card->controls_rwsem); + snd_card_unref(card); + } + } +} +EXPORT_SYMBOL_GPL(snd_ctl_register_layer); + +/** + * snd_ctl_disconnect_layer - disconnect control layer + * @lops: operation structure + * + * It is expected that the information about tracked cards + * is freed before this call (the disconnect callback is + * not called here). + */ +void snd_ctl_disconnect_layer(struct snd_ctl_layer_ops *lops) +{ + struct snd_ctl_layer_ops *lops2, *prev_lops2; + + down_write(&snd_ctl_layer_rwsem); + for (lops2 = snd_ctl_layer, prev_lops2 = NULL; lops2; lops2 = lops2->next) { + if (lops2 == lops) { + if (!prev_lops2) + snd_ctl_layer = lops->next; + else + prev_lops2->next = lops->next; + break; + } + prev_lops2 = lops2; + } + up_write(&snd_ctl_layer_rwsem); +} +EXPORT_SYMBOL_GPL(snd_ctl_disconnect_layer); + +/* + * INIT PART + */ + +static const struct file_operations snd_ctl_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_ctl_read, + .open = snd_ctl_open, + .release = snd_ctl_release, + .llseek = no_llseek, + .poll = snd_ctl_poll, + .unlocked_ioctl = snd_ctl_ioctl, + .compat_ioctl = snd_ctl_ioctl_compat, + .fasync = snd_ctl_fasync, +}; + +/* + * registration of the control device + */ +static int snd_ctl_dev_register(struct snd_device *device) +{ + struct snd_card *card = device->device_data; + struct snd_ctl_layer_ops *lops; + int err; + + err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, + &snd_ctl_f_ops, card, card->ctl_dev); + if (err < 0) + return err; + down_read(&card->controls_rwsem); + down_read(&snd_ctl_layer_rwsem); + for (lops = snd_ctl_layer; lops; lops = lops->next) + lops->lregister(card); + up_read(&snd_ctl_layer_rwsem); + up_read(&card->controls_rwsem); + return 0; +} + +/* + * disconnection of the control device + */ +static int snd_ctl_dev_disconnect(struct snd_device *device) +{ + struct snd_card *card = device->device_data; + struct snd_ctl_file *ctl; + struct snd_ctl_layer_ops *lops; + unsigned long flags; + + read_lock_irqsave(&card->ctl_files_rwlock, flags); + list_for_each_entry(ctl, &card->ctl_files, list) { + wake_up(&ctl->change_sleep); + snd_kill_fasync(ctl->fasync, SIGIO, POLL_ERR); + } + read_unlock_irqrestore(&card->ctl_files_rwlock, flags); + + down_read(&card->controls_rwsem); + down_read(&snd_ctl_layer_rwsem); + for (lops = snd_ctl_layer; lops; lops = lops->next) + lops->ldisconnect(card); + up_read(&snd_ctl_layer_rwsem); + up_read(&card->controls_rwsem); + + return snd_unregister_device(card->ctl_dev); +} + +/* + * free all controls + */ +static int snd_ctl_dev_free(struct snd_device *device) +{ + struct snd_card *card = device->device_data; + struct snd_kcontrol *control; + + down_write(&card->controls_rwsem); + while (!list_empty(&card->controls)) { + control = snd_kcontrol(card->controls.next); + __snd_ctl_remove(card, control, false); + } + +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + xa_destroy(&card->ctl_numids); + xa_destroy(&card->ctl_hash); +#endif + up_write(&card->controls_rwsem); + put_device(card->ctl_dev); + return 0; +} + +/* + * create control core: + * called from init.c + */ +int snd_ctl_create(struct snd_card *card) +{ + static const struct snd_device_ops ops = { + .dev_free = snd_ctl_dev_free, + .dev_register = snd_ctl_dev_register, + .dev_disconnect = snd_ctl_dev_disconnect, + }; + int err; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS)) + return -ENXIO; + + err = snd_device_alloc(&card->ctl_dev, card); + if (err < 0) + return err; + dev_set_name(card->ctl_dev, "controlC%d", card->number); + + err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops); + if (err < 0) + put_device(card->ctl_dev); + return err; +} + +/* + * Frequently used control callbacks/helpers + */ + +/** + * snd_ctl_boolean_mono_info - Helper function for a standard boolean info + * callback with a mono channel + * @kcontrol: the kcontrol instance + * @uinfo: info to store + * + * This is a function that can be used as info callback for a standard + * boolean control with a single mono channel. + * + * Return: Zero (always successful) + */ +int snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} +EXPORT_SYMBOL(snd_ctl_boolean_mono_info); + +/** + * snd_ctl_boolean_stereo_info - Helper function for a standard boolean info + * callback with stereo two channels + * @kcontrol: the kcontrol instance + * @uinfo: info to store + * + * This is a function that can be used as info callback for a standard + * boolean control with stereo two channels. + * + * Return: Zero (always successful) + */ +int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} +EXPORT_SYMBOL(snd_ctl_boolean_stereo_info); + +/** + * snd_ctl_enum_info - fills the info structure for an enumerated control + * @info: the structure to be filled + * @channels: the number of the control's channels; often one + * @items: the number of control values; also the size of @names + * @names: an array containing the names of all control values + * + * Sets all required fields in @info to their appropriate values. + * If the control's accessibility is not the default (readable and writable), + * the caller has to fill @info->access. + * + * Return: Zero (always successful) + */ +int snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels, + unsigned int items, const char *const names[]) +{ + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = channels; + info->value.enumerated.items = items; + if (!items) + return 0; + if (info->value.enumerated.item >= items) + info->value.enumerated.item = items - 1; + WARN(strlen(names[info->value.enumerated.item]) >= sizeof(info->value.enumerated.name), + "ALSA: too long item name '%s'\n", + names[info->value.enumerated.item]); + strscpy(info->value.enumerated.name, + names[info->value.enumerated.item], + sizeof(info->value.enumerated.name)); + return 0; +} +EXPORT_SYMBOL(snd_ctl_enum_info); diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c new file mode 100644 index 0000000000..0e8b1bfb04 --- /dev/null +++ b/sound/core/control_compat.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * compat ioctls for control API + * + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +/* this file included from control.c */ + +#include <linux/compat.h> +#include <linux/slab.h> + +struct snd_ctl_elem_list32 { + u32 offset; + u32 space; + u32 used; + u32 count; + u32 pids; + unsigned char reserved[50]; +} /* don't set packed attribute here */; + +static int snd_ctl_elem_list_compat(struct snd_card *card, + struct snd_ctl_elem_list32 __user *data32) +{ + struct snd_ctl_elem_list data = {}; + compat_caddr_t ptr; + int err; + + /* offset, space, used, count */ + if (copy_from_user(&data, data32, 4 * sizeof(u32))) + return -EFAULT; + /* pids */ + if (get_user(ptr, &data32->pids)) + return -EFAULT; + data.pids = compat_ptr(ptr); + err = snd_ctl_elem_list(card, &data); + if (err < 0) + return err; + /* copy the result */ + if (copy_to_user(data32, &data, 4 * sizeof(u32))) + return -EFAULT; + return 0; +} + +/* + * control element info + * it uses union, so the things are not easy.. + */ + +struct snd_ctl_elem_info32 { + struct snd_ctl_elem_id id; // the size of struct is same + s32 type; + u32 access; + u32 count; + s32 owner; + union { + struct { + s32 min; + s32 max; + s32 step; + } integer; + struct { + u64 min; + u64 max; + u64 step; + } integer64; + struct { + u32 items; + u32 item; + char name[64]; + u64 names_ptr; + u32 names_length; + } enumerated; + unsigned char reserved[128]; + } value; + unsigned char reserved[64]; +} __attribute__((packed)); + +static int snd_ctl_elem_info_compat(struct snd_ctl_file *ctl, + struct snd_ctl_elem_info32 __user *data32) +{ + struct snd_ctl_elem_info *data; + int err; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (! data) + return -ENOMEM; + + err = -EFAULT; + /* copy id */ + if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) + goto error; + /* we need to copy the item index. + * hope this doesn't break anything.. + */ + if (get_user(data->value.enumerated.item, &data32->value.enumerated.item)) + goto error; + + err = snd_ctl_elem_info(ctl, data); + if (err < 0) + goto error; + /* restore info to 32bit */ + err = -EFAULT; + /* id, type, access, count */ + if (copy_to_user(&data32->id, &data->id, sizeof(data->id)) || + copy_to_user(&data32->type, &data->type, 3 * sizeof(u32))) + goto error; + if (put_user(data->owner, &data32->owner)) + goto error; + switch (data->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + if (put_user(data->value.integer.min, &data32->value.integer.min) || + put_user(data->value.integer.max, &data32->value.integer.max) || + put_user(data->value.integer.step, &data32->value.integer.step)) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + if (copy_to_user(&data32->value.integer64, + &data->value.integer64, + sizeof(data->value.integer64))) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + if (copy_to_user(&data32->value.enumerated, + &data->value.enumerated, + sizeof(data->value.enumerated))) + goto error; + break; + default: + break; + } + err = 0; + error: + kfree(data); + return err; +} + +/* read / write */ +struct snd_ctl_elem_value32 { + struct snd_ctl_elem_id id; + unsigned int indirect; /* bit-field causes misalignment */ + union { + s32 integer[128]; + unsigned char data[512]; +#ifndef CONFIG_X86_64 + s64 integer64[64]; +#endif + } value; + unsigned char reserved[128]; +}; + +#ifdef CONFIG_X86_X32_ABI +/* x32 has a different alignment for 64bit values from ia32 */ +struct snd_ctl_elem_value_x32 { + struct snd_ctl_elem_id id; + unsigned int indirect; /* bit-field causes misalignment */ + union { + s32 integer[128]; + unsigned char data[512]; + s64 integer64[64]; + } value; + unsigned char reserved[128]; +}; +#endif /* CONFIG_X86_X32_ABI */ + +/* get the value type and count of the control */ +static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, + int *countp) +{ + struct snd_kcontrol *kctl; + struct snd_ctl_elem_info *info; + int err; + + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, id); + if (! kctl) { + up_read(&card->controls_rwsem); + return -ENOENT; + } + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (info == NULL) { + up_read(&card->controls_rwsem); + return -ENOMEM; + } + info->id = *id; + err = snd_power_ref_and_wait(card); + if (!err) + err = kctl->info(kctl, info); + snd_power_unref(card); + up_read(&card->controls_rwsem); + if (err >= 0) { + err = info->type; + *countp = info->count; + } + kfree(info); + return err; +} + +static int get_elem_size(snd_ctl_elem_type_t type, int count) +{ + switch (type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + return sizeof(s64) * count; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + return sizeof(int) * count; + case SNDRV_CTL_ELEM_TYPE_BYTES: + return 512; + case SNDRV_CTL_ELEM_TYPE_IEC958: + return sizeof(struct snd_aes_iec958); + default: + return -1; + } +} + +static int copy_ctl_value_from_user(struct snd_card *card, + struct snd_ctl_elem_value *data, + void __user *userdata, + void __user *valuep, + int *typep, int *countp) +{ + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, type, size; + int count; + unsigned int indirect; + + if (copy_from_user(&data->id, &data32->id, sizeof(data->id))) + return -EFAULT; + if (get_user(indirect, &data32->indirect)) + return -EFAULT; + if (indirect) + return -EINVAL; + type = get_ctl_type(card, &data->id, &count); + if (type < 0) + return type; + + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; + if (get_user(val, &intp[i])) + return -EFAULT; + data->value.integer.value[i] = val; + } + } else { + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); + if (size < 0) { + dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); + return -EINVAL; + } + if (copy_from_user(data->value.bytes.data, valuep, size)) + return -EFAULT; + } + + *typep = type; + *countp = count; + return 0; +} + +/* restore the value to 32bit */ +static int copy_ctl_value_to_user(void __user *userdata, + void __user *valuep, + struct snd_ctl_elem_value *data, + int type, int count) +{ + struct snd_ctl_elem_value32 __user *data32 = userdata; + int i, size; + + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < count; i++) { + s32 __user *intp = valuep; + int val; + val = data->value.integer.value[i]; + if (put_user(val, &intp[i])) + return -EFAULT; + } + } else { + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); + if (copy_to_user(valuep, data->value.bytes.data, size)) + return -EFAULT; + } + if (copy_to_user(&data32->id, &data->id, sizeof(data32->id))) + return -EFAULT; + return 0; +} + +static int ctl_elem_read_user(struct snd_card *card, + void __user *userdata, void __user *valuep) +{ + struct snd_ctl_elem_value *data; + int err, type, count; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + err = copy_ctl_value_from_user(card, data, userdata, valuep, + &type, &count); + if (err < 0) + goto error; + + err = snd_ctl_elem_read(card, data); + if (err < 0) + goto error; + err = copy_ctl_value_to_user(userdata, valuep, data, type, count); + error: + kfree(data); + return err; +} + +static int ctl_elem_write_user(struct snd_ctl_file *file, + void __user *userdata, void __user *valuep) +{ + struct snd_ctl_elem_value *data; + struct snd_card *card = file->card; + int err, type, count; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + err = copy_ctl_value_from_user(card, data, userdata, valuep, + &type, &count); + if (err < 0) + goto error; + + err = snd_ctl_elem_write(card, file, data); + if (err < 0) + goto error; + err = copy_ctl_value_to_user(userdata, valuep, data, type, count); + error: + kfree(data); + return err; +} + +static int snd_ctl_elem_read_user_compat(struct snd_card *card, + struct snd_ctl_elem_value32 __user *data32) +{ + return ctl_elem_read_user(card, data32, &data32->value); +} + +static int snd_ctl_elem_write_user_compat(struct snd_ctl_file *file, + struct snd_ctl_elem_value32 __user *data32) +{ + return ctl_elem_write_user(file, data32, &data32->value); +} + +#ifdef CONFIG_X86_X32_ABI +static int snd_ctl_elem_read_user_x32(struct snd_card *card, + struct snd_ctl_elem_value_x32 __user *data32) +{ + return ctl_elem_read_user(card, data32, &data32->value); +} + +static int snd_ctl_elem_write_user_x32(struct snd_ctl_file *file, + struct snd_ctl_elem_value_x32 __user *data32) +{ + return ctl_elem_write_user(file, data32, &data32->value); +} +#endif /* CONFIG_X86_X32_ABI */ + +/* add or replace a user control */ +static int snd_ctl_elem_add_compat(struct snd_ctl_file *file, + struct snd_ctl_elem_info32 __user *data32, + int replace) +{ + struct snd_ctl_elem_info *data; + int err; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (! data) + return -ENOMEM; + + err = -EFAULT; + /* id, type, access, count */ \ + if (copy_from_user(&data->id, &data32->id, sizeof(data->id)) || + copy_from_user(&data->type, &data32->type, 3 * sizeof(u32))) + goto error; + if (get_user(data->owner, &data32->owner)) + goto error; + switch (data->type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + case SNDRV_CTL_ELEM_TYPE_INTEGER: + if (get_user(data->value.integer.min, &data32->value.integer.min) || + get_user(data->value.integer.max, &data32->value.integer.max) || + get_user(data->value.integer.step, &data32->value.integer.step)) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER64: + if (copy_from_user(&data->value.integer64, + &data32->value.integer64, + sizeof(data->value.integer64))) + goto error; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + if (copy_from_user(&data->value.enumerated, + &data32->value.enumerated, + sizeof(data->value.enumerated))) + goto error; + data->value.enumerated.names_ptr = + (uintptr_t)compat_ptr(data->value.enumerated.names_ptr); + break; + default: + break; + } + err = snd_ctl_elem_add(file, data, replace); + error: + kfree(data); + return err; +} + +enum { + SNDRV_CTL_IOCTL_ELEM_LIST32 = _IOWR('U', 0x10, struct snd_ctl_elem_list32), + SNDRV_CTL_IOCTL_ELEM_INFO32 = _IOWR('U', 0x11, struct snd_ctl_elem_info32), + SNDRV_CTL_IOCTL_ELEM_READ32 = _IOWR('U', 0x12, struct snd_ctl_elem_value32), + SNDRV_CTL_IOCTL_ELEM_WRITE32 = _IOWR('U', 0x13, struct snd_ctl_elem_value32), + SNDRV_CTL_IOCTL_ELEM_ADD32 = _IOWR('U', 0x17, struct snd_ctl_elem_info32), + SNDRV_CTL_IOCTL_ELEM_REPLACE32 = _IOWR('U', 0x18, struct snd_ctl_elem_info32), +#ifdef CONFIG_X86_X32_ABI + SNDRV_CTL_IOCTL_ELEM_READ_X32 = _IOWR('U', 0x12, struct snd_ctl_elem_value_x32), + SNDRV_CTL_IOCTL_ELEM_WRITE_X32 = _IOWR('U', 0x13, struct snd_ctl_elem_value_x32), +#endif /* CONFIG_X86_X32_ABI */ +}; + +static inline long snd_ctl_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_ctl_file *ctl; + struct snd_kctl_ioctl *p; + void __user *argp = compat_ptr(arg); + int err; + + ctl = file->private_data; + if (snd_BUG_ON(!ctl || !ctl->card)) + return -ENXIO; + + switch (cmd) { + case SNDRV_CTL_IOCTL_PVERSION: + case SNDRV_CTL_IOCTL_CARD_INFO: + case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS: + case SNDRV_CTL_IOCTL_POWER: + case SNDRV_CTL_IOCTL_POWER_STATE: + case SNDRV_CTL_IOCTL_ELEM_LOCK: + case SNDRV_CTL_IOCTL_ELEM_UNLOCK: + case SNDRV_CTL_IOCTL_ELEM_REMOVE: + case SNDRV_CTL_IOCTL_TLV_READ: + case SNDRV_CTL_IOCTL_TLV_WRITE: + case SNDRV_CTL_IOCTL_TLV_COMMAND: + return snd_ctl_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_CTL_IOCTL_ELEM_LIST32: + return snd_ctl_elem_list_compat(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_INFO32: + return snd_ctl_elem_info_compat(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_READ32: + return snd_ctl_elem_read_user_compat(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE32: + return snd_ctl_elem_write_user_compat(ctl, argp); + case SNDRV_CTL_IOCTL_ELEM_ADD32: + return snd_ctl_elem_add_compat(ctl, argp, 0); + case SNDRV_CTL_IOCTL_ELEM_REPLACE32: + return snd_ctl_elem_add_compat(ctl, argp, 1); +#ifdef CONFIG_X86_X32_ABI + case SNDRV_CTL_IOCTL_ELEM_READ_X32: + return snd_ctl_elem_read_user_x32(ctl->card, argp); + case SNDRV_CTL_IOCTL_ELEM_WRITE_X32: + return snd_ctl_elem_write_user_x32(ctl, argp); +#endif /* CONFIG_X86_X32_ABI */ + } + + down_read(&snd_ioctl_rwsem); + list_for_each_entry(p, &snd_control_compat_ioctls, list) { + if (p->fioctl) { + err = p->fioctl(ctl->card, ctl, cmd, arg); + if (err != -ENOIOCTLCMD) { + up_read(&snd_ioctl_rwsem); + return err; + } + } + } + up_read(&snd_ioctl_rwsem); + return -ENOIOCTLCMD; +} diff --git a/sound/core/control_led.c b/sound/core/control_led.c new file mode 100644 index 0000000000..a78eb48927 --- /dev/null +++ b/sound/core/control_led.c @@ -0,0 +1,794 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LED state routines for driver control interface + * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/leds.h> +#include <sound/core.h> +#include <sound/control.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("ALSA control interface to LED trigger code."); +MODULE_LICENSE("GPL"); + +#define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \ + >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1) + +#define to_led_card_dev(_dev) \ + container_of(_dev, struct snd_ctl_led_card, dev) + +enum snd_ctl_led_mode { + MODE_FOLLOW_MUTE = 0, + MODE_FOLLOW_ROUTE, + MODE_OFF, + MODE_ON, +}; + +struct snd_ctl_led_card { + struct device dev; + int number; + struct snd_ctl_led *led; +}; + +struct snd_ctl_led { + struct device dev; + struct list_head controls; + const char *name; + unsigned int group; + enum led_audio trigger_type; + enum snd_ctl_led_mode mode; + struct snd_ctl_led_card *cards[SNDRV_CARDS]; +}; + +struct snd_ctl_led_ctl { + struct list_head list; + struct snd_card *card; + unsigned int access; + struct snd_kcontrol *kctl; + unsigned int index_offset; +}; + +static DEFINE_MUTEX(snd_ctl_led_mutex); +static bool snd_ctl_led_card_valid[SNDRV_CARDS]; +static struct snd_ctl_led snd_ctl_leds[MAX_LED] = { + { + .name = "speaker", + .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, + .trigger_type = LED_AUDIO_MUTE, + .mode = MODE_FOLLOW_MUTE, + }, + { + .name = "mic", + .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, + .trigger_type = LED_AUDIO_MICMUTE, + .mode = MODE_FOLLOW_MUTE, + }, +}; + +static void snd_ctl_led_sysfs_add(struct snd_card *card); +static void snd_ctl_led_sysfs_remove(struct snd_card *card); + +#define UPDATE_ROUTE(route, cb) \ + do { \ + int route2 = (cb); \ + if (route2 >= 0) \ + route = route < 0 ? route2 : (route | route2); \ + } while (0) + +static inline unsigned int access_to_group(unsigned int access) +{ + return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >> + SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1; +} + +static inline unsigned int group_to_access(unsigned int group) +{ + return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT; +} + +static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access) +{ + unsigned int group = access_to_group(access); + if (group >= MAX_LED) + return NULL; + return &snd_ctl_leds[group]; +} + +/* + * A note for callers: + * The two static variables info and value are protected using snd_ctl_led_mutex. + */ +static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl) +{ + static struct snd_ctl_elem_info info; + static struct snd_ctl_elem_value value; + struct snd_kcontrol *kctl = lctl->kctl; + unsigned int i; + int result; + + memset(&info, 0, sizeof(info)); + info.id = kctl->id; + info.id.index += lctl->index_offset; + info.id.numid += lctl->index_offset; + result = kctl->info(kctl, &info); + if (result < 0) + return -1; + memset(&value, 0, sizeof(value)); + value.id = info.id; + result = kctl->get(kctl, &value); + if (result < 0) + return -1; + if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || + info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + for (i = 0; i < info.count; i++) + if (value.value.integer.value[i] != info.value.integer.min) + return 1; + } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { + for (i = 0; i < info.count; i++) + if (value.value.integer64.value[i] != info.value.integer64.min) + return 1; + } + return 0; +} + +static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, + struct snd_kcontrol *kctl, unsigned int ioff) +{ + struct snd_ctl_led *led; + struct snd_ctl_led_ctl *lctl; + int route; + bool found; + + led = snd_ctl_led_get_by_access(access); + if (!led) + return; + route = -1; + found = false; + mutex_lock(&snd_ctl_led_mutex); + /* the card may not be registered (active) at this point */ + if (card && !snd_ctl_led_card_valid[card->number]) { + mutex_unlock(&snd_ctl_led_mutex); + return; + } + list_for_each_entry(lctl, &led->controls, list) { + if (lctl->kctl == kctl && lctl->index_offset == ioff) + found = true; + UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); + } + if (!found && kctl && card) { + lctl = kzalloc(sizeof(*lctl), GFP_KERNEL); + if (lctl) { + lctl->card = card; + lctl->access = access; + lctl->kctl = kctl; + lctl->index_offset = ioff; + list_add(&lctl->list, &led->controls); + UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); + } + } + mutex_unlock(&snd_ctl_led_mutex); + switch (led->mode) { + case MODE_OFF: route = 1; break; + case MODE_ON: route = 0; break; + case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; + case MODE_FOLLOW_MUTE: /* noop */ break; + } + if (route >= 0) + ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON); +} + +static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff) +{ + struct list_head *controls; + struct snd_ctl_led_ctl *lctl; + unsigned int group; + + for (group = 0; group < MAX_LED; group++) { + controls = &snd_ctl_leds[group].controls; + list_for_each_entry(lctl, controls, list) + if (lctl->kctl == kctl && lctl->index_offset == ioff) + return lctl; + } + return NULL; +} + +static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff, + unsigned int access) +{ + struct snd_ctl_led_ctl *lctl; + unsigned int ret = 0; + + mutex_lock(&snd_ctl_led_mutex); + lctl = snd_ctl_led_find(kctl, ioff); + if (lctl && (access == 0 || access != lctl->access)) { + ret = lctl->access; + list_del(&lctl->list); + kfree(lctl); + } + mutex_unlock(&snd_ctl_led_mutex); + return ret; +} + +static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask, + struct snd_kcontrol *kctl, unsigned int ioff) +{ + struct snd_kcontrol_volatile *vd; + unsigned int access, access2; + + if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) { + access = snd_ctl_led_remove(kctl, ioff, 0); + if (access) + snd_ctl_led_set_state(card, access, NULL, 0); + } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) { + vd = &kctl->vd[ioff]; + access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; + access2 = snd_ctl_led_remove(kctl, ioff, access); + if (access2) + snd_ctl_led_set_state(card, access2, NULL, 0); + if (access) + snd_ctl_led_set_state(card, access, kctl, ioff); + } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD | + SNDRV_CTL_EVENT_MASK_VALUE)) != 0) { + vd = &kctl->vd[ioff]; + access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; + if (access) + snd_ctl_led_set_state(card, access, kctl, ioff); + } +} + +static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, + unsigned int group, bool set) +{ + struct snd_card *card; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + unsigned int ioff, access, new_access; + int err = 0; + + card = snd_card_ref(card_number); + if (card) { + down_write(&card->controls_rwsem); + kctl = snd_ctl_find_id_locked(card, id); + if (kctl) { + ioff = snd_ctl_get_ioff(kctl, id); + vd = &kctl->vd[ioff]; + access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; + if (access != 0 && access != group_to_access(group)) { + err = -EXDEV; + goto unlock; + } + new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; + if (set) + new_access |= group_to_access(group); + if (new_access != vd->access) { + vd->access = new_access; + snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff); + } + } else { + err = -ENOENT; + } +unlock: + up_write(&card->controls_rwsem); + snd_card_unref(card); + } else { + err = -ENXIO; + } + return err; +} + +static void snd_ctl_led_refresh(void) +{ + unsigned int group; + + for (group = 0; group < MAX_LED; group++) + snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); +} + +static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl) +{ + list_del(&lctl->list); + kfree(lctl); +} + +static void snd_ctl_led_clean(struct snd_card *card) +{ + unsigned int group; + struct snd_ctl_led *led; + struct snd_ctl_led_ctl *lctl; + + for (group = 0; group < MAX_LED; group++) { + led = &snd_ctl_leds[group]; +repeat: + list_for_each_entry(lctl, &led->controls, list) + if (!card || lctl->card == card) { + snd_ctl_led_ctl_destroy(lctl); + goto repeat; + } + } +} + +static int snd_ctl_led_reset(int card_number, unsigned int group) +{ + struct snd_card *card; + struct snd_ctl_led *led; + struct snd_ctl_led_ctl *lctl; + struct snd_kcontrol_volatile *vd; + bool change = false; + + card = snd_card_ref(card_number); + if (!card) + return -ENXIO; + + mutex_lock(&snd_ctl_led_mutex); + if (!snd_ctl_led_card_valid[card_number]) { + mutex_unlock(&snd_ctl_led_mutex); + snd_card_unref(card); + return -ENXIO; + } + led = &snd_ctl_leds[group]; +repeat: + list_for_each_entry(lctl, &led->controls, list) + if (lctl->card == card) { + vd = &lctl->kctl->vd[lctl->index_offset]; + vd->access &= ~group_to_access(group); + snd_ctl_led_ctl_destroy(lctl); + change = true; + goto repeat; + } + mutex_unlock(&snd_ctl_led_mutex); + if (change) + snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); + snd_card_unref(card); + return 0; +} + +static void snd_ctl_led_register(struct snd_card *card) +{ + struct snd_kcontrol *kctl; + unsigned int ioff; + + if (snd_BUG_ON(card->number < 0 || + card->number >= ARRAY_SIZE(snd_ctl_led_card_valid))) + return; + mutex_lock(&snd_ctl_led_mutex); + snd_ctl_led_card_valid[card->number] = true; + mutex_unlock(&snd_ctl_led_mutex); + /* the register callback is already called with held card->controls_rwsem */ + list_for_each_entry(kctl, &card->controls, list) + for (ioff = 0; ioff < kctl->count; ioff++) + snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff); + snd_ctl_led_refresh(); + snd_ctl_led_sysfs_add(card); +} + +static void snd_ctl_led_disconnect(struct snd_card *card) +{ + snd_ctl_led_sysfs_remove(card); + mutex_lock(&snd_ctl_led_mutex); + snd_ctl_led_card_valid[card->number] = false; + snd_ctl_led_clean(card); + mutex_unlock(&snd_ctl_led_mutex); + snd_ctl_led_refresh(); +} + +static void snd_ctl_led_card_release(struct device *dev) +{ + struct snd_ctl_led_card *led_card = to_led_card_dev(dev); + + kfree(led_card); +} + +static void snd_ctl_led_release(struct device *dev) +{ +} + +static void snd_ctl_led_dev_release(struct device *dev) +{ +} + +/* + * sysfs + */ + +static ssize_t mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); + const char *str = NULL; + + switch (led->mode) { + case MODE_FOLLOW_MUTE: str = "follow-mute"; break; + case MODE_FOLLOW_ROUTE: str = "follow-route"; break; + case MODE_ON: str = "on"; break; + case MODE_OFF: str = "off"; break; + } + return sysfs_emit(buf, "%s\n", str); +} + +static ssize_t mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); + char _buf[16]; + size_t l = min(count, sizeof(_buf) - 1); + enum snd_ctl_led_mode mode; + + memcpy(_buf, buf, l); + _buf[l] = '\0'; + if (strstr(_buf, "mute")) + mode = MODE_FOLLOW_MUTE; + else if (strstr(_buf, "route")) + mode = MODE_FOLLOW_ROUTE; + else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0) + mode = MODE_OFF; + else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0) + mode = MODE_ON; + else + return count; + + mutex_lock(&snd_ctl_led_mutex); + led->mode = mode; + mutex_unlock(&snd_ctl_led_mutex); + + snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0); + return count; +} + +static ssize_t brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); + + return sysfs_emit(buf, "%u\n", ledtrig_audio_get(led->trigger_type)); +} + +static DEVICE_ATTR_RW(mode); +static DEVICE_ATTR_RO(brightness); + +static struct attribute *snd_ctl_led_dev_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_brightness.attr, + NULL, +}; + +static const struct attribute_group snd_ctl_led_dev_attr_group = { + .attrs = snd_ctl_led_dev_attrs, +}; + +static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { + &snd_ctl_led_dev_attr_group, + NULL, +}; + +static char *find_eos(char *s) +{ + while (*s && *s != ',') + s++; + if (*s) + s++; + return s; +} + +static char *parse_uint(char *s, unsigned int *val) +{ + unsigned long long res; + if (kstrtoull(s, 10, &res)) + res = 0; + *val = res; + return find_eos(s); +} + +static char *parse_string(char *s, char *val, size_t val_size) +{ + if (*s == '"' || *s == '\'') { + char c = *s; + s++; + while (*s && *s != c) { + if (val_size > 1) { + *val++ = *s; + val_size--; + } + s++; + } + } else { + while (*s && *s != ',') { + if (val_size > 1) { + *val++ = *s; + val_size--; + } + s++; + } + } + *val = '\0'; + if (*s) + s++; + return s; +} + +static char *parse_iface(char *s, snd_ctl_elem_iface_t *val) +{ + if (!strncasecmp(s, "card", 4)) + *val = SNDRV_CTL_ELEM_IFACE_CARD; + else if (!strncasecmp(s, "mixer", 5)) + *val = SNDRV_CTL_ELEM_IFACE_MIXER; + return find_eos(s); +} + +/* + * These types of input strings are accepted: + * + * unsigned integer - numid (equivaled to numid=UINT) + * string - basic mixer name (equivalent to iface=MIXER,name=STR) + * numid=UINT + * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT] + */ +static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count, + bool attach) +{ + char buf2[256], *s, *os; + struct snd_ctl_elem_id id; + int err; + + if (strscpy(buf2, buf, sizeof(buf2)) < 0) + return -E2BIG; + memset(&id, 0, sizeof(id)); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + s = buf2; + while (*s) { + os = s; + if (!strncasecmp(s, "numid=", 6)) { + s = parse_uint(s + 6, &id.numid); + } else if (!strncasecmp(s, "iface=", 6)) { + s = parse_iface(s + 6, &id.iface); + } else if (!strncasecmp(s, "device=", 7)) { + s = parse_uint(s + 7, &id.device); + } else if (!strncasecmp(s, "subdevice=", 10)) { + s = parse_uint(s + 10, &id.subdevice); + } else if (!strncasecmp(s, "name=", 5)) { + s = parse_string(s + 5, id.name, sizeof(id.name)); + } else if (!strncasecmp(s, "index=", 6)) { + s = parse_uint(s + 6, &id.index); + } else if (s == buf2) { + while (*s) { + if (*s < '0' || *s > '9') + break; + s++; + } + if (*s == '\0') + parse_uint(buf2, &id.numid); + else { + for (; *s >= ' '; s++); + *s = '\0'; + strscpy(id.name, buf2, sizeof(id.name)); + } + break; + } + if (*s == ',') + s++; + if (s == os) + break; + } + + err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach); + if (err < 0) + return err; + + return count; +} + +static ssize_t attach_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + return set_led_id(led_card, buf, count, true); +} + +static ssize_t detach_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + return set_led_id(led_card, buf, count, false); +} + +static ssize_t reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + int err; + + if (count > 0 && buf[0] == '1') { + err = snd_ctl_led_reset(led_card->number, led_card->led->group); + if (err < 0) + return err; + } + return count; +} + +static ssize_t list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + struct snd_card *card; + struct snd_ctl_led_ctl *lctl; + size_t l = 0; + + card = snd_card_ref(led_card->number); + if (!card) + return -ENXIO; + down_read(&card->controls_rwsem); + mutex_lock(&snd_ctl_led_mutex); + if (snd_ctl_led_card_valid[led_card->number]) { + list_for_each_entry(lctl, &led_card->led->controls, list) { + if (lctl->card != card) + continue; + if (l) + l += sysfs_emit_at(buf, l, " "); + l += sysfs_emit_at(buf, l, "%u", + lctl->kctl->id.numid + lctl->index_offset); + } + } + mutex_unlock(&snd_ctl_led_mutex); + up_read(&card->controls_rwsem); + snd_card_unref(card); + return l; +} + +static DEVICE_ATTR_WO(attach); +static DEVICE_ATTR_WO(detach); +static DEVICE_ATTR_WO(reset); +static DEVICE_ATTR_RO(list); + +static struct attribute *snd_ctl_led_card_attrs[] = { + &dev_attr_attach.attr, + &dev_attr_detach.attr, + &dev_attr_reset.attr, + &dev_attr_list.attr, + NULL, +}; + +static const struct attribute_group snd_ctl_led_card_attr_group = { + .attrs = snd_ctl_led_card_attrs, +}; + +static const struct attribute_group *snd_ctl_led_card_attr_groups[] = { + &snd_ctl_led_card_attr_group, + NULL, +}; + +static struct device snd_ctl_led_dev; + +static void snd_ctl_led_sysfs_add(struct snd_card *card) +{ + unsigned int group; + struct snd_ctl_led_card *led_card; + struct snd_ctl_led *led; + char link_name[32]; + + for (group = 0; group < MAX_LED; group++) { + led = &snd_ctl_leds[group]; + led_card = kzalloc(sizeof(*led_card), GFP_KERNEL); + if (!led_card) + goto cerr2; + led_card->number = card->number; + led_card->led = led; + device_initialize(&led_card->dev); + led_card->dev.release = snd_ctl_led_card_release; + if (dev_set_name(&led_card->dev, "card%d", card->number) < 0) + goto cerr; + led_card->dev.parent = &led->dev; + led_card->dev.groups = snd_ctl_led_card_attr_groups; + if (device_add(&led_card->dev)) + goto cerr; + led->cards[card->number] = led_card; + snprintf(link_name, sizeof(link_name), "led-%s", led->name); + WARN(sysfs_create_link(&card->ctl_dev->kobj, &led_card->dev.kobj, link_name), + "can't create symlink to controlC%i device\n", card->number); + WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"), + "can't create symlink to card%i\n", card->number); + + continue; +cerr: + put_device(&led_card->dev); +cerr2: + printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number); + } +} + +static void snd_ctl_led_sysfs_remove(struct snd_card *card) +{ + unsigned int group; + struct snd_ctl_led_card *led_card; + struct snd_ctl_led *led; + char link_name[32]; + + for (group = 0; group < MAX_LED; group++) { + led = &snd_ctl_leds[group]; + led_card = led->cards[card->number]; + if (!led_card) + continue; + snprintf(link_name, sizeof(link_name), "led-%s", led->name); + sysfs_remove_link(&card->ctl_dev->kobj, link_name); + sysfs_remove_link(&led_card->dev.kobj, "card"); + device_unregister(&led_card->dev); + led->cards[card->number] = NULL; + } +} + +/* + * Control layer registration + */ +static struct snd_ctl_layer_ops snd_ctl_led_lops = { + .module_name = SND_CTL_LAYER_MODULE_LED, + .lregister = snd_ctl_led_register, + .ldisconnect = snd_ctl_led_disconnect, + .lnotify = snd_ctl_led_notify, +}; + +static int __init snd_ctl_led_init(void) +{ + struct snd_ctl_led *led; + unsigned int group; + + device_initialize(&snd_ctl_led_dev); + snd_ctl_led_dev.class = &sound_class; + snd_ctl_led_dev.release = snd_ctl_led_dev_release; + dev_set_name(&snd_ctl_led_dev, "ctl-led"); + if (device_add(&snd_ctl_led_dev)) { + put_device(&snd_ctl_led_dev); + return -ENOMEM; + } + for (group = 0; group < MAX_LED; group++) { + led = &snd_ctl_leds[group]; + INIT_LIST_HEAD(&led->controls); + device_initialize(&led->dev); + led->dev.parent = &snd_ctl_led_dev; + led->dev.release = snd_ctl_led_release; + led->dev.groups = snd_ctl_led_dev_attr_groups; + dev_set_name(&led->dev, led->name); + if (device_add(&led->dev)) { + put_device(&led->dev); + for (; group > 0; group--) { + led = &snd_ctl_leds[group - 1]; + device_unregister(&led->dev); + } + device_unregister(&snd_ctl_led_dev); + return -ENOMEM; + } + } + snd_ctl_register_layer(&snd_ctl_led_lops); + return 0; +} + +static void __exit snd_ctl_led_exit(void) +{ + struct snd_ctl_led *led; + struct snd_card *card; + unsigned int group, card_number; + + snd_ctl_disconnect_layer(&snd_ctl_led_lops); + for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { + if (!snd_ctl_led_card_valid[card_number]) + continue; + card = snd_card_ref(card_number); + if (card) { + snd_ctl_led_sysfs_remove(card); + snd_card_unref(card); + } + } + for (group = 0; group < MAX_LED; group++) { + led = &snd_ctl_leds[group]; + device_unregister(&led->dev); + } + device_unregister(&snd_ctl_led_dev); + snd_ctl_led_clean(NULL); +} + +module_init(snd_ctl_led_init) +module_exit(snd_ctl_led_exit) diff --git a/sound/core/ctljack.c b/sound/core/ctljack.c new file mode 100644 index 0000000000..709b1a9c2c --- /dev/null +++ b/sound/core/ctljack.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Helper functions for jack-detection kcontrols + * + * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/kernel.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/control.h> + +#define jack_detect_kctl_info snd_ctl_boolean_mono_info + +static int jack_detect_kctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kcontrol->private_value; + return 0; +} + +static const struct snd_kcontrol_new jack_detect_kctl = { + /* name is filled later */ + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = jack_detect_kctl_info, + .get = jack_detect_kctl_get, +}; + +static int get_available_index(struct snd_card *card, const char *name) +{ + struct snd_ctl_elem_id sid; + + memset(&sid, 0, sizeof(sid)); + + sid.index = 0; + sid.iface = SNDRV_CTL_ELEM_IFACE_CARD; + strscpy(sid.name, name, sizeof(sid.name)); + + while (snd_ctl_find_id(card, &sid)) { + sid.index++; + /* reset numid; otherwise snd_ctl_find_id() hits this again */ + sid.numid = 0; + } + + return sid.index; +} + +static void jack_kctl_name_gen(char *name, const char *src_name, int size) +{ + size_t count = strlen(src_name); + bool need_cat = true; + + /* remove redundant " Jack" from src_name */ + if (count >= 5) + need_cat = strncmp(&src_name[count - 5], " Jack", 5) ? true : false; + + snprintf(name, size, need_cat ? "%s Jack" : "%s", src_name); + +} + +struct snd_kcontrol * +snd_kctl_jack_new(const char *name, struct snd_card *card) +{ + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1(&jack_detect_kctl, NULL); + if (!kctl) + return NULL; + + jack_kctl_name_gen(kctl->id.name, name, sizeof(kctl->id.name)); + kctl->id.index = get_available_index(card, kctl->id.name); + kctl->private_value = 0; + return kctl; +} + +void snd_kctl_jack_report(struct snd_card *card, + struct snd_kcontrol *kctl, bool status) +{ + if (kctl->private_value == status) + return; + kctl->private_value = status; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); +} diff --git a/sound/core/device.c b/sound/core/device.c new file mode 100644 index 0000000000..b57d80a170 --- /dev/null +++ b/sound/core/device.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Device management routines + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/export.h> +#include <linux/errno.h> +#include <sound/core.h> + +/** + * snd_device_new - create an ALSA device component + * @card: the card instance + * @type: the device type, SNDRV_DEV_XXX + * @device_data: the data pointer of this device + * @ops: the operator table + * + * Creates a new device component for the given data pointer. + * The device will be assigned to the card and managed together + * by the card. + * + * The data pointer plays a role as the identifier, too, so the + * pointer address must be unique and unchanged. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_device_new(struct snd_card *card, enum snd_device_type type, + void *device_data, const struct snd_device_ops *ops) +{ + struct snd_device *dev; + struct list_head *p; + + if (snd_BUG_ON(!card || !device_data || !ops)) + return -ENXIO; + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + INIT_LIST_HEAD(&dev->list); + dev->card = card; + dev->type = type; + dev->state = SNDRV_DEV_BUILD; + dev->device_data = device_data; + dev->ops = ops; + + /* insert the entry in an incrementally sorted list */ + list_for_each_prev(p, &card->devices) { + struct snd_device *pdev = list_entry(p, struct snd_device, list); + if ((unsigned int)pdev->type <= (unsigned int)type) + break; + } + + list_add(&dev->list, p); + return 0; +} +EXPORT_SYMBOL(snd_device_new); + +static void __snd_device_disconnect(struct snd_device *dev) +{ + if (dev->state == SNDRV_DEV_REGISTERED) { + if (dev->ops->dev_disconnect && + dev->ops->dev_disconnect(dev)) + dev_err(dev->card->dev, "device disconnect failure\n"); + dev->state = SNDRV_DEV_DISCONNECTED; + } +} + +static void __snd_device_free(struct snd_device *dev) +{ + /* unlink */ + list_del(&dev->list); + + __snd_device_disconnect(dev); + if (dev->ops->dev_free) { + if (dev->ops->dev_free(dev)) + dev_err(dev->card->dev, "device free failure\n"); + } + kfree(dev); +} + +static struct snd_device *look_for_dev(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + list_for_each_entry(dev, &card->devices, list) + if (dev->device_data == device_data) + return dev; + + return NULL; +} + +/** + * snd_device_disconnect - disconnect the device + * @card: the card instance + * @device_data: the data pointer to disconnect + * + * Turns the device into the disconnection state, invoking + * dev_disconnect callback, if the device was already registered. + * + * Usually called from snd_card_disconnect(). + * + * Return: Zero if successful, or a negative error code on failure or if the + * device not found. + */ +void snd_device_disconnect(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card || !device_data)) + return; + dev = look_for_dev(card, device_data); + if (dev) + __snd_device_disconnect(dev); + else + dev_dbg(card->dev, "device disconnect %p (from %pS), not found\n", + device_data, __builtin_return_address(0)); +} +EXPORT_SYMBOL_GPL(snd_device_disconnect); + +/** + * snd_device_free - release the device from the card + * @card: the card instance + * @device_data: the data pointer to release + * + * Removes the device from the list on the card and invokes the + * callbacks, dev_disconnect and dev_free, corresponding to the state. + * Then release the device. + */ +void snd_device_free(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card || !device_data)) + return; + dev = look_for_dev(card, device_data); + if (dev) + __snd_device_free(dev); + else + dev_dbg(card->dev, "device free %p (from %pS), not found\n", + device_data, __builtin_return_address(0)); +} +EXPORT_SYMBOL(snd_device_free); + +static int __snd_device_register(struct snd_device *dev) +{ + if (dev->state == SNDRV_DEV_BUILD) { + if (dev->ops->dev_register) { + int err = dev->ops->dev_register(dev); + if (err < 0) + return err; + } + dev->state = SNDRV_DEV_REGISTERED; + } + return 0; +} + +/** + * snd_device_register - register the device + * @card: the card instance + * @device_data: the data pointer to register + * + * Registers the device which was already created via + * snd_device_new(). Usually this is called from snd_card_register(), + * but it can be called later if any new devices are created after + * invocation of snd_card_register(). + * + * Return: Zero if successful, or a negative error code on failure or if the + * device not found. + */ +int snd_device_register(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card || !device_data)) + return -ENXIO; + dev = look_for_dev(card, device_data); + if (dev) + return __snd_device_register(dev); + snd_BUG(); + return -ENXIO; +} +EXPORT_SYMBOL(snd_device_register); + +/* + * register all the devices on the card. + * called from init.c + */ +int snd_device_register_all(struct snd_card *card) +{ + struct snd_device *dev; + int err; + + if (snd_BUG_ON(!card)) + return -ENXIO; + list_for_each_entry(dev, &card->devices, list) { + err = __snd_device_register(dev); + if (err < 0) + return err; + } + return 0; +} + +/* + * disconnect all the devices on the card. + * called from init.c + */ +void snd_device_disconnect_all(struct snd_card *card) +{ + struct snd_device *dev; + + if (snd_BUG_ON(!card)) + return; + list_for_each_entry_reverse(dev, &card->devices, list) + __snd_device_disconnect(dev); +} + +/* + * release all the devices on the card. + * called from init.c + */ +void snd_device_free_all(struct snd_card *card) +{ + struct snd_device *dev, *next; + + if (snd_BUG_ON(!card)) + return; + list_for_each_entry_safe_reverse(dev, next, &card->devices, list) { + /* exception: free ctl and lowlevel stuff later */ + if (dev->type == SNDRV_DEV_CONTROL || + dev->type == SNDRV_DEV_LOWLEVEL) + continue; + __snd_device_free(dev); + } + + /* free all */ + list_for_each_entry_safe_reverse(dev, next, &card->devices, list) + __snd_device_free(dev); +} + +/** + * snd_device_get_state - Get the current state of the given device + * @card: the card instance + * @device_data: the data pointer to release + * + * Returns the current state of the given device object. For the valid + * device, either @SNDRV_DEV_BUILD, @SNDRV_DEV_REGISTERED or + * @SNDRV_DEV_DISCONNECTED is returned. + * Or for a non-existing device, -1 is returned as an error. + * + * Return: the current state, or -1 if not found + */ +int snd_device_get_state(struct snd_card *card, void *device_data) +{ + struct snd_device *dev; + + dev = look_for_dev(card, device_data); + if (dev) + return dev->state; + return -1; +} +EXPORT_SYMBOL_GPL(snd_device_get_state); diff --git a/sound/core/hrtimer.c b/sound/core/hrtimer.c new file mode 100644 index 0000000000..e97ff8cccb --- /dev/null +++ b/sound/core/hrtimer.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA timer back-end using hrtimer + * Copyright (C) 2008 Takashi Iwai + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/hrtimer.h> +#include <sound/core.h> +#include <sound/timer.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA hrtimer backend"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER)); + +#define NANO_SEC 1000000000UL /* 10^9 in sec */ +static unsigned int resolution; + +struct snd_hrtimer { + struct snd_timer *timer; + struct hrtimer hrt; + bool in_callback; +}; + +static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) +{ + struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt); + struct snd_timer *t = stime->timer; + ktime_t delta; + unsigned long ticks; + enum hrtimer_restart ret = HRTIMER_NORESTART; + + spin_lock(&t->lock); + if (!t->running) + goto out; /* fast path */ + stime->in_callback = true; + ticks = t->sticks; + spin_unlock(&t->lock); + + /* calculate the drift */ + delta = ktime_sub(hrt->base->get_time(), hrtimer_get_expires(hrt)); + if (delta > 0) + ticks += ktime_divns(delta, ticks * resolution); + + snd_timer_interrupt(stime->timer, ticks); + + spin_lock(&t->lock); + if (t->running) { + hrtimer_add_expires_ns(hrt, t->sticks * resolution); + ret = HRTIMER_RESTART; + } + + stime->in_callback = false; + out: + spin_unlock(&t->lock); + return ret; +} + +static int snd_hrtimer_open(struct snd_timer *t) +{ + struct snd_hrtimer *stime; + + stime = kzalloc(sizeof(*stime), GFP_KERNEL); + if (!stime) + return -ENOMEM; + hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + stime->timer = t; + stime->hrt.function = snd_hrtimer_callback; + t->private_data = stime; + return 0; +} + +static int snd_hrtimer_close(struct snd_timer *t) +{ + struct snd_hrtimer *stime = t->private_data; + + if (stime) { + spin_lock_irq(&t->lock); + t->running = 0; /* just to be sure */ + stime->in_callback = 1; /* skip start/stop */ + spin_unlock_irq(&t->lock); + + hrtimer_cancel(&stime->hrt); + kfree(stime); + t->private_data = NULL; + } + return 0; +} + +static int snd_hrtimer_start(struct snd_timer *t) +{ + struct snd_hrtimer *stime = t->private_data; + + if (stime->in_callback) + return 0; + hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution), + HRTIMER_MODE_REL); + return 0; +} + +static int snd_hrtimer_stop(struct snd_timer *t) +{ + struct snd_hrtimer *stime = t->private_data; + + if (stime->in_callback) + return 0; + hrtimer_try_to_cancel(&stime->hrt); + return 0; +} + +static const struct snd_timer_hardware hrtimer_hw __initconst = { + .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_WORK, + .open = snd_hrtimer_open, + .close = snd_hrtimer_close, + .start = snd_hrtimer_start, + .stop = snd_hrtimer_stop, +}; + +/* + * entry functions + */ + +static struct snd_timer *mytimer; + +static int __init snd_hrtimer_init(void) +{ + struct snd_timer *timer; + int err; + + resolution = hrtimer_resolution; + + /* Create a new timer and set up the fields */ + err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER, + &timer); + if (err < 0) + return err; + + timer->module = THIS_MODULE; + strcpy(timer->name, "HR timer"); + timer->hw = hrtimer_hw; + timer->hw.resolution = resolution; + timer->hw.ticks = NANO_SEC / resolution; + timer->max_instances = 100; /* lower the limit */ + + err = snd_timer_global_register(timer); + if (err < 0) { + snd_timer_global_free(timer); + return err; + } + mytimer = timer; /* remember this */ + + return 0; +} + +static void __exit snd_hrtimer_exit(void) +{ + if (mytimer) { + snd_timer_global_free(mytimer); + mytimer = NULL; + } +} + +module_init(snd_hrtimer_init); +module_exit(snd_hrtimer_exit); diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c new file mode 100644 index 0000000000..de7476034f --- /dev/null +++ b/sound/core/hwdep.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware dependent layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/major.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/sched/signal.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/minors.h> +#include <sound/hwdep.h> +#include <sound/info.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Hardware dependent layer"); +MODULE_LICENSE("GPL"); + +static LIST_HEAD(snd_hwdep_devices); +static DEFINE_MUTEX(register_mutex); + +static int snd_hwdep_dev_free(struct snd_device *device); +static int snd_hwdep_dev_register(struct snd_device *device); +static int snd_hwdep_dev_disconnect(struct snd_device *device); + + +static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device) +{ + struct snd_hwdep *hwdep; + + list_for_each_entry(hwdep, &snd_hwdep_devices, list) + if (hwdep->card == card && hwdep->device == device) + return hwdep; + return NULL; +} + +static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.llseek) + return hw->ops.llseek(hw, file, offset, orig); + return -ENXIO; +} + +static ssize_t snd_hwdep_read(struct file * file, char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.read) + return hw->ops.read(hw, buf, count, offset); + return -ENXIO; +} + +static ssize_t snd_hwdep_write(struct file * file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.write) + return hw->ops.write(hw, buf, count, offset); + return -ENXIO; +} + +static int snd_hwdep_open(struct inode *inode, struct file * file) +{ + int major = imajor(inode); + struct snd_hwdep *hw; + int err; + wait_queue_entry_t wait; + + if (major == snd_major) { + hw = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_HWDEP); +#ifdef CONFIG_SND_OSSEMUL + } else if (major == SOUND_MAJOR) { + hw = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_DMFM); +#endif + } else + return -ENXIO; + if (hw == NULL) + return -ENODEV; + + if (!try_module_get(hw->card->module)) { + snd_card_unref(hw->card); + return -EFAULT; + } + + init_waitqueue_entry(&wait, current); + add_wait_queue(&hw->open_wait, &wait); + mutex_lock(&hw->open_mutex); + while (1) { + if (hw->exclusive && hw->used > 0) { + err = -EBUSY; + break; + } + if (!hw->ops.open) { + err = 0; + break; + } + err = hw->ops.open(hw, file); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&hw->open_mutex); + schedule(); + mutex_lock(&hw->open_mutex); + if (hw->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&hw->open_wait, &wait); + if (err >= 0) { + err = snd_card_file_add(hw->card, file); + if (err >= 0) { + file->private_data = hw; + hw->used++; + } else { + if (hw->ops.release) + hw->ops.release(hw, file); + } + } + mutex_unlock(&hw->open_mutex); + if (err < 0) + module_put(hw->card->module); + snd_card_unref(hw->card); + return err; +} + +static int snd_hwdep_release(struct inode *inode, struct file * file) +{ + int err = 0; + struct snd_hwdep *hw = file->private_data; + struct module *mod = hw->card->module; + + mutex_lock(&hw->open_mutex); + if (hw->ops.release) + err = hw->ops.release(hw, file); + if (hw->used > 0) + hw->used--; + mutex_unlock(&hw->open_mutex); + wake_up(&hw->open_wait); + + snd_card_file_remove(hw->card, file); + module_put(mod); + return err; +} + +static __poll_t snd_hwdep_poll(struct file * file, poll_table * wait) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.poll) + return hw->ops.poll(hw, file, wait); + return 0; +} + +static int snd_hwdep_info(struct snd_hwdep *hw, + struct snd_hwdep_info __user *_info) +{ + struct snd_hwdep_info info; + + memset(&info, 0, sizeof(info)); + info.card = hw->card->number; + strscpy(info.id, hw->id, sizeof(info.id)); + strscpy(info.name, hw->name, sizeof(info.name)); + info.iface = hw->iface; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_hwdep_dsp_status(struct snd_hwdep *hw, + struct snd_hwdep_dsp_status __user *_info) +{ + struct snd_hwdep_dsp_status info; + int err; + + if (! hw->ops.dsp_status) + return -ENXIO; + memset(&info, 0, sizeof(info)); + info.dsp_loaded = hw->dsp_loaded; + err = hw->ops.dsp_status(hw, &info); + if (err < 0) + return err; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_hwdep_dsp_load(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image *info) +{ + int err; + + if (! hw->ops.dsp_load) + return -ENXIO; + if (info->index >= 32) + return -EINVAL; + /* check whether the dsp was already loaded */ + if (hw->dsp_loaded & (1u << info->index)) + return -EBUSY; + err = hw->ops.dsp_load(hw, info); + if (err < 0) + return err; + hw->dsp_loaded |= (1u << info->index); + return 0; +} + +static int snd_hwdep_dsp_load_user(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image __user *_info) +{ + struct snd_hwdep_dsp_image info = {}; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + return snd_hwdep_dsp_load(hw, &info); +} + + +static long snd_hwdep_ioctl(struct file * file, unsigned int cmd, + unsigned long arg) +{ + struct snd_hwdep *hw = file->private_data; + void __user *argp = (void __user *)arg; + switch (cmd) { + case SNDRV_HWDEP_IOCTL_PVERSION: + return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp); + case SNDRV_HWDEP_IOCTL_INFO: + return snd_hwdep_info(hw, argp); + case SNDRV_HWDEP_IOCTL_DSP_STATUS: + return snd_hwdep_dsp_status(hw, argp); + case SNDRV_HWDEP_IOCTL_DSP_LOAD: + return snd_hwdep_dsp_load_user(hw, argp); + } + if (hw->ops.ioctl) + return hw->ops.ioctl(hw, file, cmd, arg); + return -ENOTTY; +} + +static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma) +{ + struct snd_hwdep *hw = file->private_data; + if (hw->ops.mmap) + return hw->ops.mmap(hw, file, vma); + return -ENXIO; +} + +static int snd_hwdep_control_ioctl(struct snd_card *card, + struct snd_ctl_file * control, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)arg)) + return -EFAULT; + mutex_lock(®ister_mutex); + + if (device < 0) + device = 0; + else if (device < SNDRV_MINOR_HWDEPS) + device++; + else + device = SNDRV_MINOR_HWDEPS; + + while (device < SNDRV_MINOR_HWDEPS) { + if (snd_hwdep_search(card, device)) + break; + device++; + } + if (device >= SNDRV_MINOR_HWDEPS) + device = -1; + mutex_unlock(®ister_mutex); + if (put_user(device, (int __user *)arg)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_HWDEP_INFO: + { + struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg; + int device, err; + struct snd_hwdep *hwdep; + + if (get_user(device, &info->device)) + return -EFAULT; + mutex_lock(®ister_mutex); + hwdep = snd_hwdep_search(card, device); + if (hwdep) + err = snd_hwdep_info(hwdep, info); + else + err = -ENXIO; + mutex_unlock(®ister_mutex); + return err; + } + } + return -ENOIOCTLCMD; +} + +#ifdef CONFIG_COMPAT +#include "hwdep_compat.c" +#else +#define snd_hwdep_ioctl_compat NULL +#endif + +/* + + */ + +static const struct file_operations snd_hwdep_f_ops = +{ + .owner = THIS_MODULE, + .llseek = snd_hwdep_llseek, + .read = snd_hwdep_read, + .write = snd_hwdep_write, + .open = snd_hwdep_open, + .release = snd_hwdep_release, + .poll = snd_hwdep_poll, + .unlocked_ioctl = snd_hwdep_ioctl, + .compat_ioctl = snd_hwdep_ioctl_compat, + .mmap = snd_hwdep_mmap, +}; + +static void snd_hwdep_free(struct snd_hwdep *hwdep) +{ + if (!hwdep) + return; + if (hwdep->private_free) + hwdep->private_free(hwdep); + put_device(hwdep->dev); + kfree(hwdep); +} + +/** + * snd_hwdep_new - create a new hwdep instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero-based) + * @rhwdep: the pointer to store the new hwdep instance + * + * Creates a new hwdep instance with the given index on the card. + * The callbacks (hwdep->ops) must be set on the returned instance + * after this call manually by the caller. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_hwdep_new(struct snd_card *card, char *id, int device, + struct snd_hwdep **rhwdep) +{ + struct snd_hwdep *hwdep; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_hwdep_dev_free, + .dev_register = snd_hwdep_dev_register, + .dev_disconnect = snd_hwdep_dev_disconnect, + }; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (rhwdep) + *rhwdep = NULL; + hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL); + if (!hwdep) + return -ENOMEM; + + init_waitqueue_head(&hwdep->open_wait); + mutex_init(&hwdep->open_mutex); + hwdep->card = card; + hwdep->device = device; + if (id) + strscpy(hwdep->id, id, sizeof(hwdep->id)); + + err = snd_device_alloc(&hwdep->dev, card); + if (err < 0) { + snd_hwdep_free(hwdep); + return err; + } + + dev_set_name(hwdep->dev, "hwC%iD%i", card->number, device); +#ifdef CONFIG_SND_OSSEMUL + hwdep->oss_type = -1; +#endif + + err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops); + if (err < 0) { + snd_hwdep_free(hwdep); + return err; + } + + if (rhwdep) + *rhwdep = hwdep; + return 0; +} +EXPORT_SYMBOL(snd_hwdep_new); + +static int snd_hwdep_dev_free(struct snd_device *device) +{ + snd_hwdep_free(device->device_data); + return 0; +} + +static int snd_hwdep_dev_register(struct snd_device *device) +{ + struct snd_hwdep *hwdep = device->device_data; + struct snd_card *card = hwdep->card; + int err; + + mutex_lock(®ister_mutex); + if (snd_hwdep_search(card, hwdep->device)) { + mutex_unlock(®ister_mutex); + return -EBUSY; + } + list_add_tail(&hwdep->list, &snd_hwdep_devices); + err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP, + hwdep->card, hwdep->device, + &snd_hwdep_f_ops, hwdep, hwdep->dev); + if (err < 0) { + dev_err(hwdep->dev, "unable to register\n"); + list_del(&hwdep->list); + mutex_unlock(®ister_mutex); + return err; + } + +#ifdef CONFIG_SND_OSSEMUL + hwdep->ossreg = 0; + if (hwdep->oss_type >= 0) { + if (hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM && + hwdep->device) + dev_warn(hwdep->dev, + "only hwdep device 0 can be registered as OSS direct FM device!\n"); + else if (snd_register_oss_device(hwdep->oss_type, + card, hwdep->device, + &snd_hwdep_f_ops, hwdep) < 0) + dev_warn(hwdep->dev, + "unable to register OSS compatibility device\n"); + else + hwdep->ossreg = 1; + } +#endif + mutex_unlock(®ister_mutex); + return 0; +} + +static int snd_hwdep_dev_disconnect(struct snd_device *device) +{ + struct snd_hwdep *hwdep = device->device_data; + + if (snd_BUG_ON(!hwdep)) + return -ENXIO; + mutex_lock(®ister_mutex); + if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) { + mutex_unlock(®ister_mutex); + return -EINVAL; + } + mutex_lock(&hwdep->open_mutex); + wake_up(&hwdep->open_wait); +#ifdef CONFIG_SND_OSSEMUL + if (hwdep->ossreg) + snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device); +#endif + snd_unregister_device(hwdep->dev); + list_del_init(&hwdep->list); + mutex_unlock(&hwdep->open_mutex); + mutex_unlock(®ister_mutex); + return 0; +} + +#ifdef CONFIG_SND_PROC_FS +/* + * Info interface + */ + +static void snd_hwdep_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_hwdep *hwdep; + + mutex_lock(®ister_mutex); + list_for_each_entry(hwdep, &snd_hwdep_devices, list) + snd_iprintf(buffer, "%02i-%02i: %s\n", + hwdep->card->number, hwdep->device, hwdep->name); + mutex_unlock(®ister_mutex); +} + +static struct snd_info_entry *snd_hwdep_proc_entry; + +static void __init snd_hwdep_proc_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL); + if (entry) { + entry->c.text.read = snd_hwdep_proc_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_hwdep_proc_entry = entry; +} + +static void __exit snd_hwdep_proc_done(void) +{ + snd_info_free_entry(snd_hwdep_proc_entry); +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_hwdep_proc_init() +#define snd_hwdep_proc_done() +#endif /* CONFIG_SND_PROC_FS */ + + +/* + * ENTRY functions + */ + +static int __init alsa_hwdep_init(void) +{ + snd_hwdep_proc_init(); + snd_ctl_register_ioctl(snd_hwdep_control_ioctl); + snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl); + return 0; +} + +static void __exit alsa_hwdep_exit(void) +{ + snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl); + snd_hwdep_proc_done(); +} + +module_init(alsa_hwdep_init) +module_exit(alsa_hwdep_exit) diff --git a/sound/core/hwdep_compat.c b/sound/core/hwdep_compat.c new file mode 100644 index 0000000000..a0b76706c0 --- /dev/null +++ b/sound/core/hwdep_compat.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 32bit -> 64bit ioctl wrapper for hwdep API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +/* This file is included from hwdep.c */ + +#include <linux/compat.h> + +struct snd_hwdep_dsp_image32 { + u32 index; + unsigned char name[64]; + u32 image; /* pointer */ + u32 length; + u32 driver_data; +} /* don't set packed attribute here */; + +static int snd_hwdep_dsp_load_compat(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image32 __user *src) +{ + struct snd_hwdep_dsp_image info = {}; + compat_caddr_t ptr; + + if (copy_from_user(&info, src, 4 + 64) || + get_user(ptr, &src->image) || + get_user(info.length, &src->length) || + get_user(info.driver_data, &src->driver_data)) + return -EFAULT; + info.image = compat_ptr(ptr); + + return snd_hwdep_dsp_load(hw, &info); +} + +enum { + SNDRV_HWDEP_IOCTL_DSP_LOAD32 = _IOW('H', 0x03, struct snd_hwdep_dsp_image32) +}; + +static long snd_hwdep_ioctl_compat(struct file * file, unsigned int cmd, + unsigned long arg) +{ + struct snd_hwdep *hw = file->private_data; + void __user *argp = compat_ptr(arg); + switch (cmd) { + case SNDRV_HWDEP_IOCTL_PVERSION: + case SNDRV_HWDEP_IOCTL_INFO: + case SNDRV_HWDEP_IOCTL_DSP_STATUS: + return snd_hwdep_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_HWDEP_IOCTL_DSP_LOAD32: + return snd_hwdep_dsp_load_compat(hw, argp); + } + if (hw->ops.ioctl_compat) + return hw->ops.ioctl_compat(hw, file, cmd, arg); + return -ENOIOCTLCMD; +} diff --git a/sound/core/info.c b/sound/core/info.c new file mode 100644 index 0000000000..e2f302e55b --- /dev/null +++ b/sound/core/info.c @@ -0,0 +1,920 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Information interface for ALSA driver + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/time.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <linux/utsname.h> +#include <linux/proc_fs.h> +#include <linux/mutex.h> + +int snd_info_check_reserved_words(const char *str) +{ + static const char * const reserved[] = + { + "version", + "meminfo", + "memdebug", + "detect", + "devices", + "oss", + "cards", + "timers", + "synth", + "pcm", + "seq", + NULL + }; + const char * const *xstr = reserved; + + while (*xstr) { + if (!strcmp(*xstr, str)) + return 0; + xstr++; + } + if (!strncmp(str, "card", 4)) + return 0; + return 1; +} + +static DEFINE_MUTEX(info_mutex); + +struct snd_info_private_data { + struct snd_info_buffer *rbuffer; + struct snd_info_buffer *wbuffer; + struct snd_info_entry *entry; + void *file_private_data; +}; + +static int snd_info_version_init(void); +static void snd_info_clear_entries(struct snd_info_entry *entry); + +/* + + */ + +static struct snd_info_entry *snd_proc_root; +struct snd_info_entry *snd_seq_root; +EXPORT_SYMBOL(snd_seq_root); + +#ifdef CONFIG_SND_OSSEMUL +struct snd_info_entry *snd_oss_root; +#endif + +static int alloc_info_private(struct snd_info_entry *entry, + struct snd_info_private_data **ret) +{ + struct snd_info_private_data *data; + + if (!entry || !entry->p) + return -ENODEV; + if (!try_module_get(entry->module)) + return -EFAULT; + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + module_put(entry->module); + return -ENOMEM; + } + data->entry = entry; + *ret = data; + return 0; +} + +static bool valid_pos(loff_t pos, size_t count) +{ + if (pos < 0 || (long) pos != pos || (ssize_t) count < 0) + return false; + if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos) + return false; + return true; +} + +/* + * file ops for binary proc files + */ +static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig) +{ + struct snd_info_private_data *data; + struct snd_info_entry *entry; + loff_t ret = -EINVAL, size; + + data = file->private_data; + entry = data->entry; + mutex_lock(&entry->access); + if (entry->c.ops->llseek) { + ret = entry->c.ops->llseek(entry, + data->file_private_data, + file, offset, orig); + goto out; + } + + size = entry->size; + switch (orig) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += file->f_pos; + break; + case SEEK_END: + if (!size) + goto out; + offset += size; + break; + default: + goto out; + } + if (offset < 0) + goto out; + if (size && offset > size) + offset = size; + file->f_pos = offset; + ret = offset; + out: + mutex_unlock(&entry->access); + return ret; +} + +static ssize_t snd_info_entry_read(struct file *file, char __user *buffer, + size_t count, loff_t * offset) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + size_t size; + loff_t pos; + + pos = *offset; + if (!valid_pos(pos, count)) + return -EIO; + if (pos >= entry->size) + return 0; + size = entry->size - pos; + size = min(count, size); + size = entry->c.ops->read(entry, data->file_private_data, + file, buffer, size, pos); + if ((ssize_t) size > 0) + *offset = pos + size; + return size; +} + +static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer, + size_t count, loff_t * offset) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + ssize_t size = 0; + loff_t pos; + + pos = *offset; + if (!valid_pos(pos, count)) + return -EIO; + if (count > 0) { + size_t maxsize = entry->size - pos; + count = min(count, maxsize); + size = entry->c.ops->write(entry, data->file_private_data, + file, buffer, count, pos); + } + if (size > 0) + *offset = pos + size; + return size; +} + +static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + __poll_t mask = 0; + + if (entry->c.ops->poll) + return entry->c.ops->poll(entry, + data->file_private_data, + file, wait); + if (entry->c.ops->read) + mask |= EPOLLIN | EPOLLRDNORM; + if (entry->c.ops->write) + mask |= EPOLLOUT | EPOLLWRNORM; + return mask; +} + +static long snd_info_entry_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + + if (!entry->c.ops->ioctl) + return -ENOTTY; + return entry->c.ops->ioctl(entry, data->file_private_data, + file, cmd, arg); +} + +static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct inode *inode = file_inode(file); + struct snd_info_private_data *data; + struct snd_info_entry *entry; + + data = file->private_data; + if (data == NULL) + return 0; + entry = data->entry; + if (!entry->c.ops->mmap) + return -ENXIO; + return entry->c.ops->mmap(entry, data->file_private_data, + inode, file, vma); +} + +static int snd_info_entry_open(struct inode *inode, struct file *file) +{ + struct snd_info_entry *entry = pde_data(inode); + struct snd_info_private_data *data; + int mode, err; + + mutex_lock(&info_mutex); + err = alloc_info_private(entry, &data); + if (err < 0) + goto unlock; + + mode = file->f_flags & O_ACCMODE; + if (((mode == O_RDONLY || mode == O_RDWR) && !entry->c.ops->read) || + ((mode == O_WRONLY || mode == O_RDWR) && !entry->c.ops->write)) { + err = -ENODEV; + goto error; + } + + if (entry->c.ops->open) { + err = entry->c.ops->open(entry, mode, &data->file_private_data); + if (err < 0) + goto error; + } + + file->private_data = data; + mutex_unlock(&info_mutex); + return 0; + + error: + kfree(data); + module_put(entry->module); + unlock: + mutex_unlock(&info_mutex); + return err; +} + +static int snd_info_entry_release(struct inode *inode, struct file *file) +{ + struct snd_info_private_data *data = file->private_data; + struct snd_info_entry *entry = data->entry; + + if (entry->c.ops->release) + entry->c.ops->release(entry, file->f_flags & O_ACCMODE, + data->file_private_data); + module_put(entry->module); + kfree(data); + return 0; +} + +static const struct proc_ops snd_info_entry_operations = +{ + .proc_lseek = snd_info_entry_llseek, + .proc_read = snd_info_entry_read, + .proc_write = snd_info_entry_write, + .proc_poll = snd_info_entry_poll, + .proc_ioctl = snd_info_entry_ioctl, + .proc_mmap = snd_info_entry_mmap, + .proc_open = snd_info_entry_open, + .proc_release = snd_info_entry_release, +}; + +/* + * file ops for text proc files + */ +static ssize_t snd_info_text_entry_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *offset) +{ + struct seq_file *m = file->private_data; + struct snd_info_private_data *data = m->private; + struct snd_info_entry *entry = data->entry; + struct snd_info_buffer *buf; + loff_t pos; + size_t next; + int err = 0; + + if (!entry->c.text.write) + return -EIO; + pos = *offset; + if (!valid_pos(pos, count)) + return -EIO; + next = pos + count; + /* don't handle too large text inputs */ + if (next > 16 * 1024) + return -EIO; + mutex_lock(&entry->access); + buf = data->wbuffer; + if (!buf) { + data->wbuffer = buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto error; + } + } + if (next > buf->len) { + char *nbuf = kvzalloc(PAGE_ALIGN(next), GFP_KERNEL); + if (!nbuf) { + err = -ENOMEM; + goto error; + } + kvfree(buf->buffer); + buf->buffer = nbuf; + buf->len = PAGE_ALIGN(next); + } + if (copy_from_user(buf->buffer + pos, buffer, count)) { + err = -EFAULT; + goto error; + } + buf->size = next; + error: + mutex_unlock(&entry->access); + if (err < 0) + return err; + *offset = next; + return count; +} + +static int snd_info_seq_show(struct seq_file *seq, void *p) +{ + struct snd_info_private_data *data = seq->private; + struct snd_info_entry *entry = data->entry; + + if (!entry->c.text.read) { + return -EIO; + } else { + data->rbuffer->buffer = (char *)seq; /* XXX hack! */ + entry->c.text.read(entry, data->rbuffer); + } + return 0; +} + +static int snd_info_text_entry_open(struct inode *inode, struct file *file) +{ + struct snd_info_entry *entry = pde_data(inode); + struct snd_info_private_data *data; + int err; + + mutex_lock(&info_mutex); + err = alloc_info_private(entry, &data); + if (err < 0) + goto unlock; + + data->rbuffer = kzalloc(sizeof(*data->rbuffer), GFP_KERNEL); + if (!data->rbuffer) { + err = -ENOMEM; + goto error; + } + if (entry->size) + err = single_open_size(file, snd_info_seq_show, data, + entry->size); + else + err = single_open(file, snd_info_seq_show, data); + if (err < 0) + goto error; + mutex_unlock(&info_mutex); + return 0; + + error: + kfree(data->rbuffer); + kfree(data); + module_put(entry->module); + unlock: + mutex_unlock(&info_mutex); + return err; +} + +static int snd_info_text_entry_release(struct inode *inode, struct file *file) +{ + struct seq_file *m = file->private_data; + struct snd_info_private_data *data = m->private; + struct snd_info_entry *entry = data->entry; + + if (data->wbuffer && entry->c.text.write) + entry->c.text.write(entry, data->wbuffer); + + single_release(inode, file); + kfree(data->rbuffer); + if (data->wbuffer) { + kvfree(data->wbuffer->buffer); + kfree(data->wbuffer); + } + + module_put(entry->module); + kfree(data); + return 0; +} + +static const struct proc_ops snd_info_text_entry_ops = +{ + .proc_open = snd_info_text_entry_open, + .proc_release = snd_info_text_entry_release, + .proc_write = snd_info_text_entry_write, + .proc_lseek = seq_lseek, + .proc_read = seq_read, +}; + +static struct snd_info_entry *create_subdir(struct module *mod, + const char *name) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(mod, name, NULL); + if (!entry) + return NULL; + entry->mode = S_IFDIR | 0555; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return NULL; + } + return entry; +} + +static struct snd_info_entry * +snd_info_create_entry(const char *name, struct snd_info_entry *parent, + struct module *module); + +int __init snd_info_init(void) +{ + snd_proc_root = snd_info_create_entry("asound", NULL, THIS_MODULE); + if (!snd_proc_root) + return -ENOMEM; + snd_proc_root->mode = S_IFDIR | 0555; + snd_proc_root->p = proc_mkdir("asound", NULL); + if (!snd_proc_root->p) + goto error; +#ifdef CONFIG_SND_OSSEMUL + snd_oss_root = create_subdir(THIS_MODULE, "oss"); + if (!snd_oss_root) + goto error; +#endif +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + snd_seq_root = create_subdir(THIS_MODULE, "seq"); + if (!snd_seq_root) + goto error; +#endif + if (snd_info_version_init() < 0 || + snd_minor_info_init() < 0 || + snd_minor_info_oss_init() < 0 || + snd_card_info_init() < 0 || + snd_info_minor_register() < 0) + goto error; + return 0; + + error: + snd_info_free_entry(snd_proc_root); + return -ENOMEM; +} + +int __exit snd_info_done(void) +{ + snd_info_free_entry(snd_proc_root); + return 0; +} + +static void snd_card_id_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_card *card = entry->private_data; + + snd_iprintf(buffer, "%s\n", card->id); +} + +/* + * create a card proc file + * called from init.c + */ +int snd_info_card_create(struct snd_card *card) +{ + char str[8]; + struct snd_info_entry *entry; + + if (snd_BUG_ON(!card)) + return -ENXIO; + + sprintf(str, "card%i", card->number); + entry = create_subdir(card->module, str); + if (!entry) + return -ENOMEM; + card->proc_root = entry; + + return snd_card_ro_proc_new(card, "id", card, snd_card_id_read); +} + +/* + * register the card proc file + * called from init.c + * can be called multiple times for reinitialization + */ +int snd_info_card_register(struct snd_card *card) +{ + struct proc_dir_entry *p; + int err; + + if (snd_BUG_ON(!card)) + return -ENXIO; + + err = snd_info_register(card->proc_root); + if (err < 0) + return err; + + if (!strcmp(card->id, card->proc_root->name)) + return 0; + + if (card->proc_root_link) + return 0; + p = proc_symlink(card->id, snd_proc_root->p, card->proc_root->name); + if (!p) + return -ENOMEM; + card->proc_root_link = p; + return 0; +} + +/* + * called on card->id change + */ +void snd_info_card_id_change(struct snd_card *card) +{ + mutex_lock(&info_mutex); + if (card->proc_root_link) { + proc_remove(card->proc_root_link); + card->proc_root_link = NULL; + } + if (strcmp(card->id, card->proc_root->name)) + card->proc_root_link = proc_symlink(card->id, + snd_proc_root->p, + card->proc_root->name); + mutex_unlock(&info_mutex); +} + +/* + * de-register the card proc file + * called from init.c + */ +void snd_info_card_disconnect(struct snd_card *card) +{ + if (!card) + return; + + proc_remove(card->proc_root_link); + if (card->proc_root) + proc_remove(card->proc_root->p); + + mutex_lock(&info_mutex); + if (card->proc_root) + snd_info_clear_entries(card->proc_root); + card->proc_root_link = NULL; + card->proc_root = NULL; + mutex_unlock(&info_mutex); +} + +/* + * release the card proc file resources + * called from init.c + */ +int snd_info_card_free(struct snd_card *card) +{ + if (!card) + return 0; + snd_info_free_entry(card->proc_root); + card->proc_root = NULL; + return 0; +} + + +/** + * snd_info_get_line - read one line from the procfs buffer + * @buffer: the procfs buffer + * @line: the buffer to store + * @len: the max. buffer size + * + * Reads one line from the buffer and stores the string. + * + * Return: Zero if successful, or 1 if error or EOF. + */ +int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len) +{ + int c; + + if (snd_BUG_ON(!buffer)) + return 1; + if (!buffer->buffer) + return 1; + if (len <= 0 || buffer->stop || buffer->error) + return 1; + while (!buffer->stop) { + c = buffer->buffer[buffer->curr++]; + if (buffer->curr >= buffer->size) + buffer->stop = 1; + if (c == '\n') + break; + if (len > 1) { + len--; + *line++ = c; + } + } + *line = '\0'; + return 0; +} +EXPORT_SYMBOL(snd_info_get_line); + +/** + * snd_info_get_str - parse a string token + * @dest: the buffer to store the string token + * @src: the original string + * @len: the max. length of token - 1 + * + * Parses the original string and copy a token to the given + * string buffer. + * + * Return: The updated pointer of the original string so that + * it can be used for the next call. + */ +const char *snd_info_get_str(char *dest, const char *src, int len) +{ + int c; + + while (*src == ' ' || *src == '\t') + src++; + if (*src == '"' || *src == '\'') { + c = *src++; + while (--len > 0 && *src && *src != c) { + *dest++ = *src++; + } + if (*src == c) + src++; + } else { + while (--len > 0 && *src && *src != ' ' && *src != '\t') { + *dest++ = *src++; + } + } + *dest = 0; + while (*src == ' ' || *src == '\t') + src++; + return src; +} +EXPORT_SYMBOL(snd_info_get_str); + +/* + * snd_info_create_entry - create an info entry + * @name: the proc file name + * @parent: the parent directory + * + * Creates an info entry with the given file name and initializes as + * the default state. + * + * Usually called from other functions such as + * snd_info_create_card_entry(). + * + * Return: The pointer of the new instance, or %NULL on failure. + */ +static struct snd_info_entry * +snd_info_create_entry(const char *name, struct snd_info_entry *parent, + struct module *module) +{ + struct snd_info_entry *entry; + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry == NULL) + return NULL; + entry->name = kstrdup(name, GFP_KERNEL); + if (entry->name == NULL) { + kfree(entry); + return NULL; + } + entry->mode = S_IFREG | 0444; + entry->content = SNDRV_INFO_CONTENT_TEXT; + mutex_init(&entry->access); + INIT_LIST_HEAD(&entry->children); + INIT_LIST_HEAD(&entry->list); + entry->parent = parent; + entry->module = module; + if (parent) { + mutex_lock(&parent->access); + list_add_tail(&entry->list, &parent->children); + mutex_unlock(&parent->access); + } + return entry; +} + +/** + * snd_info_create_module_entry - create an info entry for the given module + * @module: the module pointer + * @name: the file name + * @parent: the parent directory + * + * Creates a new info entry and assigns it to the given module. + * + * Return: The pointer of the new instance, or %NULL on failure. + */ +struct snd_info_entry *snd_info_create_module_entry(struct module * module, + const char *name, + struct snd_info_entry *parent) +{ + if (!parent) + parent = snd_proc_root; + return snd_info_create_entry(name, parent, module); +} +EXPORT_SYMBOL(snd_info_create_module_entry); + +/** + * snd_info_create_card_entry - create an info entry for the given card + * @card: the card instance + * @name: the file name + * @parent: the parent directory + * + * Creates a new info entry and assigns it to the given card. + * + * Return: The pointer of the new instance, or %NULL on failure. + */ +struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card, + const char *name, + struct snd_info_entry * parent) +{ + if (!parent) + parent = card->proc_root; + return snd_info_create_entry(name, parent, card->module); +} +EXPORT_SYMBOL(snd_info_create_card_entry); + +static void snd_info_clear_entries(struct snd_info_entry *entry) +{ + struct snd_info_entry *p; + + if (!entry->p) + return; + list_for_each_entry(p, &entry->children, list) + snd_info_clear_entries(p); + entry->p = NULL; +} + +/** + * snd_info_free_entry - release the info entry + * @entry: the info entry + * + * Releases the info entry. + */ +void snd_info_free_entry(struct snd_info_entry * entry) +{ + struct snd_info_entry *p, *n; + + if (!entry) + return; + if (entry->p) { + proc_remove(entry->p); + mutex_lock(&info_mutex); + snd_info_clear_entries(entry); + mutex_unlock(&info_mutex); + } + + /* free all children at first */ + list_for_each_entry_safe(p, n, &entry->children, list) + snd_info_free_entry(p); + + p = entry->parent; + if (p) { + mutex_lock(&p->access); + list_del(&entry->list); + mutex_unlock(&p->access); + } + kfree(entry->name); + if (entry->private_free) + entry->private_free(entry); + kfree(entry); +} +EXPORT_SYMBOL(snd_info_free_entry); + +static int __snd_info_register(struct snd_info_entry *entry) +{ + struct proc_dir_entry *root, *p = NULL; + + if (snd_BUG_ON(!entry)) + return -ENXIO; + root = entry->parent == NULL ? snd_proc_root->p : entry->parent->p; + mutex_lock(&info_mutex); + if (entry->p || !root) + goto unlock; + if (S_ISDIR(entry->mode)) { + p = proc_mkdir_mode(entry->name, entry->mode, root); + if (!p) { + mutex_unlock(&info_mutex); + return -ENOMEM; + } + } else { + const struct proc_ops *ops; + if (entry->content == SNDRV_INFO_CONTENT_DATA) + ops = &snd_info_entry_operations; + else + ops = &snd_info_text_entry_ops; + p = proc_create_data(entry->name, entry->mode, root, + ops, entry); + if (!p) { + mutex_unlock(&info_mutex); + return -ENOMEM; + } + proc_set_size(p, entry->size); + } + entry->p = p; + unlock: + mutex_unlock(&info_mutex); + return 0; +} + +/** + * snd_info_register - register the info entry + * @entry: the info entry + * + * Registers the proc info entry. + * The all children entries are registered recursively. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_info_register(struct snd_info_entry *entry) +{ + struct snd_info_entry *p; + int err; + + if (!entry->p) { + err = __snd_info_register(entry); + if (err < 0) + return err; + } + + list_for_each_entry(p, &entry->children, list) { + err = snd_info_register(p); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL(snd_info_register); + +/** + * snd_card_rw_proc_new - Create a read/write text proc file entry for the card + * @card: the card instance + * @name: the file name + * @private_data: the arbitrary private data + * @read: the read callback + * @write: the write callback, NULL for read-only + * + * This proc file entry will be registered via snd_card_register() call, and + * it will be removed automatically at the card removal, too. + * + * Return: zero if successful, or a negative error code + */ +int snd_card_rw_proc_new(struct snd_card *card, const char *name, + void *private_data, + void (*read)(struct snd_info_entry *, + struct snd_info_buffer *), + void (*write)(struct snd_info_entry *entry, + struct snd_info_buffer *buffer)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(card, name, card->proc_root); + if (!entry) + return -ENOMEM; + snd_info_set_text_ops(entry, private_data, read); + if (write) { + entry->mode |= 0200; + entry->c.text.write = write; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_card_rw_proc_new); + +/* + + */ + +static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, + "Advanced Linux Sound Architecture Driver Version k%s.\n", + init_utsname()->release); +} + +static int __init snd_info_version_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL); + if (entry == NULL) + return -ENOMEM; + entry->c.text.read = snd_info_version_read; + return snd_info_register(entry); /* freed in error path */ +} diff --git a/sound/core/info_oss.c b/sound/core/info_oss.c new file mode 100644 index 0000000000..ebc714b2f4 --- /dev/null +++ b/sound/core/info_oss.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Information interface for ALSA driver + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/string.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <linux/utsname.h> +#include <linux/mutex.h> + +/* + * OSS compatible part + */ + +static DEFINE_MUTEX(strings); +static char *snd_sndstat_strings[SNDRV_CARDS][SNDRV_OSS_INFO_DEV_COUNT]; + +int snd_oss_info_register(int dev, int num, char *string) +{ + char *x; + + if (snd_BUG_ON(dev < 0 || dev >= SNDRV_OSS_INFO_DEV_COUNT)) + return -ENXIO; + if (snd_BUG_ON(num < 0 || num >= SNDRV_CARDS)) + return -ENXIO; + mutex_lock(&strings); + if (string == NULL) { + x = snd_sndstat_strings[num][dev]; + kfree(x); + x = NULL; + } else { + x = kstrdup(string, GFP_KERNEL); + if (x == NULL) { + mutex_unlock(&strings); + return -ENOMEM; + } + } + snd_sndstat_strings[num][dev] = x; + mutex_unlock(&strings); + return 0; +} +EXPORT_SYMBOL(snd_oss_info_register); + +static int snd_sndstat_show_strings(struct snd_info_buffer *buf, char *id, int dev) +{ + int idx, ok = -1; + char *str; + + snd_iprintf(buf, "\n%s:", id); + mutex_lock(&strings); + for (idx = 0; idx < SNDRV_CARDS; idx++) { + str = snd_sndstat_strings[idx][dev]; + if (str) { + if (ok < 0) { + snd_iprintf(buf, "\n"); + ok++; + } + snd_iprintf(buf, "%i: %s\n", idx, str); + } + } + mutex_unlock(&strings); + if (ok < 0) + snd_iprintf(buf, " NOT ENABLED IN CONFIG\n"); + return ok; +} + +static void snd_sndstat_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, "Sound Driver:3.8.1a-980706 (ALSA emulation code)\n"); + snd_iprintf(buffer, "Kernel: %s %s %s %s %s\n", + init_utsname()->sysname, + init_utsname()->nodename, + init_utsname()->release, + init_utsname()->version, + init_utsname()->machine); + snd_iprintf(buffer, "Config options: 0\n"); + snd_iprintf(buffer, "\nInstalled drivers: \n"); + snd_iprintf(buffer, "Type 10: ALSA emulation\n"); + snd_iprintf(buffer, "\nCard config: \n"); + snd_card_info_read_oss(buffer); + snd_sndstat_show_strings(buffer, "Audio devices", SNDRV_OSS_INFO_DEV_AUDIO); + snd_sndstat_show_strings(buffer, "Synth devices", SNDRV_OSS_INFO_DEV_SYNTH); + snd_sndstat_show_strings(buffer, "Midi devices", SNDRV_OSS_INFO_DEV_MIDI); + snd_sndstat_show_strings(buffer, "Timers", SNDRV_OSS_INFO_DEV_TIMERS); + snd_sndstat_show_strings(buffer, "Mixers", SNDRV_OSS_INFO_DEV_MIXERS); +} + +int __init snd_info_minor_register(void) +{ + struct snd_info_entry *entry; + + memset(snd_sndstat_strings, 0, sizeof(snd_sndstat_strings)); + entry = snd_info_create_module_entry(THIS_MODULE, "sndstat", + snd_oss_root); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_sndstat_proc_read; + return snd_info_register(entry); /* freed in error path */ +} diff --git a/sound/core/init.c b/sound/core/init.c new file mode 100644 index 0000000000..22c0d217b8 --- /dev/null +++ b/sound/core/init.c @@ -0,0 +1,1177 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Initialization routines + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/file.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/ctype.h> +#include <linux/pm.h> +#include <linux/debugfs.h> +#include <linux/completion.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> + +/* monitor files for graceful shutdown (hotplug) */ +struct snd_monitor_file { + struct file *file; + const struct file_operations *disconnected_f_op; + struct list_head shutdown_list; /* still need to shutdown */ + struct list_head list; /* link of monitor files */ +}; + +static DEFINE_SPINLOCK(shutdown_lock); +static LIST_HEAD(shutdown_files); + +static const struct file_operations snd_shutdown_f_ops; + +/* locked for registering/using */ +static DECLARE_BITMAP(snd_cards_lock, SNDRV_CARDS); +static struct snd_card *snd_cards[SNDRV_CARDS]; + +static DEFINE_MUTEX(snd_card_mutex); + +static char *slots[SNDRV_CARDS]; +module_param_array(slots, charp, NULL, 0444); +MODULE_PARM_DESC(slots, "Module names assigned to the slots."); + +/* return non-zero if the given index is reserved for the given + * module via slots option + */ +static int module_slot_match(struct module *module, int idx) +{ + int match = 1; +#ifdef MODULE + const char *s1, *s2; + + if (!module || !*module->name || !slots[idx]) + return 0; + + s1 = module->name; + s2 = slots[idx]; + if (*s2 == '!') { + match = 0; /* negative match */ + s2++; + } + /* compare module name strings + * hyphens are handled as equivalent with underscore + */ + for (;;) { + char c1 = *s1++; + char c2 = *s2++; + if (c1 == '-') + c1 = '_'; + if (c2 == '-') + c2 = '_'; + if (c1 != c2) + return !match; + if (!c1) + break; + } +#endif /* MODULE */ + return match; +} + +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) +int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag); +EXPORT_SYMBOL(snd_mixer_oss_notify_callback); +#endif + +static int check_empty_slot(struct module *module, int slot) +{ + return !slots[slot] || !*slots[slot]; +} + +/* return an empty slot number (>= 0) found in the given bitmask @mask. + * @mask == -1 == 0xffffffff means: take any free slot up to 32 + * when no slot is available, return the original @mask as is. + */ +static int get_slot_from_bitmask(int mask, int (*check)(struct module *, int), + struct module *module) +{ + int slot; + + for (slot = 0; slot < SNDRV_CARDS; slot++) { + if (slot < 32 && !(mask & (1U << slot))) + continue; + if (!test_bit(slot, snd_cards_lock)) { + if (check(module, slot)) + return slot; /* found */ + } + } + return mask; /* unchanged */ +} + +/* the default release callback set in snd_device_alloc() */ +static void default_release_alloc(struct device *dev) +{ + kfree(dev); +} + +/** + * snd_device_alloc - Allocate and initialize struct device for sound devices + * @dev_p: pointer to store the allocated device + * @card: card to assign, optional + * + * For releasing the allocated device, call put_device(). + */ +int snd_device_alloc(struct device **dev_p, struct snd_card *card) +{ + struct device *dev; + + *dev_p = NULL; + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + device_initialize(dev); + if (card) + dev->parent = &card->card_dev; + dev->class = &sound_class; + dev->release = default_release_alloc; + *dev_p = dev; + return 0; +} +EXPORT_SYMBOL_GPL(snd_device_alloc); + +static int snd_card_init(struct snd_card *card, struct device *parent, + int idx, const char *xid, struct module *module, + size_t extra_size); +static int snd_card_do_free(struct snd_card *card); +static const struct attribute_group card_dev_attr_group; + +static void release_card_device(struct device *dev) +{ + snd_card_do_free(dev_to_snd_card(dev)); +} + +/** + * snd_card_new - create and initialize a soundcard structure + * @parent: the parent device object + * @idx: card index (address) [0 ... (SNDRV_CARDS-1)] + * @xid: card identification (ASCII string) + * @module: top level module for locking + * @extra_size: allocate this extra size after the main soundcard structure + * @card_ret: the pointer to store the created card instance + * + * The function allocates snd_card instance via kzalloc with the given + * space for the driver to use freely. The allocated struct is stored + * in the given card_ret pointer. + * + * Return: Zero if successful or a negative error code. + */ +int snd_card_new(struct device *parent, int idx, const char *xid, + struct module *module, int extra_size, + struct snd_card **card_ret) +{ + struct snd_card *card; + int err; + + if (snd_BUG_ON(!card_ret)) + return -EINVAL; + *card_ret = NULL; + + if (extra_size < 0) + extra_size = 0; + card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); + if (!card) + return -ENOMEM; + + err = snd_card_init(card, parent, idx, xid, module, extra_size); + if (err < 0) + return err; /* card is freed by error handler */ + + *card_ret = card; + return 0; +} +EXPORT_SYMBOL(snd_card_new); + +static void __snd_card_release(struct device *dev, void *data) +{ + snd_card_free(data); +} + +/** + * snd_devm_card_new - managed snd_card object creation + * @parent: the parent device object + * @idx: card index (address) [0 ... (SNDRV_CARDS-1)] + * @xid: card identification (ASCII string) + * @module: top level module for locking + * @extra_size: allocate this extra size after the main soundcard structure + * @card_ret: the pointer to store the created card instance + * + * This function works like snd_card_new() but manages the allocated resource + * via devres, i.e. you don't need to free explicitly. + * + * When a snd_card object is created with this function and registered via + * snd_card_register(), the very first devres action to call snd_card_free() + * is added automatically. In that way, the resource disconnection is assured + * at first, then released in the expected order. + * + * If an error happens at the probe before snd_card_register() is called and + * there have been other devres resources, you'd need to free the card manually + * via snd_card_free() call in the error; otherwise it may lead to UAF due to + * devres call orders. You can use snd_card_free_on_error() helper for + * handling it more easily. + * + * Return: zero if successful, or a negative error code + */ +int snd_devm_card_new(struct device *parent, int idx, const char *xid, + struct module *module, size_t extra_size, + struct snd_card **card_ret) +{ + struct snd_card *card; + int err; + + *card_ret = NULL; + card = devres_alloc(__snd_card_release, sizeof(*card) + extra_size, + GFP_KERNEL); + if (!card) + return -ENOMEM; + card->managed = true; + err = snd_card_init(card, parent, idx, xid, module, extra_size); + if (err < 0) { + devres_free(card); /* in managed mode, we need to free manually */ + return err; + } + + devres_add(parent, card); + *card_ret = card; + return 0; +} +EXPORT_SYMBOL_GPL(snd_devm_card_new); + +/** + * snd_card_free_on_error - a small helper for handling devm probe errors + * @dev: the managed device object + * @ret: the return code from the probe callback + * + * This function handles the explicit snd_card_free() call at the error from + * the probe callback. It's just a small helper for simplifying the error + * handling with the managed devices. + * + * Return: zero if successful, or a negative error code + */ +int snd_card_free_on_error(struct device *dev, int ret) +{ + struct snd_card *card; + + if (!ret) + return 0; + card = devres_find(dev, __snd_card_release, NULL, NULL); + if (card) + snd_card_free(card); + return ret; +} +EXPORT_SYMBOL_GPL(snd_card_free_on_error); + +static int snd_card_init(struct snd_card *card, struct device *parent, + int idx, const char *xid, struct module *module, + size_t extra_size) +{ + int err; + + if (extra_size > 0) + card->private_data = (char *)card + sizeof(struct snd_card); + if (xid) + strscpy(card->id, xid, sizeof(card->id)); + err = 0; + mutex_lock(&snd_card_mutex); + if (idx < 0) /* first check the matching module-name slot */ + idx = get_slot_from_bitmask(idx, module_slot_match, module); + if (idx < 0) /* if not matched, assign an empty slot */ + idx = get_slot_from_bitmask(idx, check_empty_slot, module); + if (idx < 0) + err = -ENODEV; + else if (idx < snd_ecards_limit) { + if (test_bit(idx, snd_cards_lock)) + err = -EBUSY; /* invalid */ + } else if (idx >= SNDRV_CARDS) + err = -ENODEV; + if (err < 0) { + mutex_unlock(&snd_card_mutex); + dev_err(parent, "cannot find the slot for index %d (range 0-%i), error: %d\n", + idx, snd_ecards_limit - 1, err); + if (!card->managed) + kfree(card); /* manually free here, as no destructor called */ + return err; + } + set_bit(idx, snd_cards_lock); /* lock it */ + if (idx >= snd_ecards_limit) + snd_ecards_limit = idx + 1; /* increase the limit */ + mutex_unlock(&snd_card_mutex); + card->dev = parent; + card->number = idx; +#ifdef MODULE + WARN_ON(!module); + card->module = module; +#endif + INIT_LIST_HEAD(&card->devices); + init_rwsem(&card->controls_rwsem); + rwlock_init(&card->ctl_files_rwlock); + INIT_LIST_HEAD(&card->controls); + INIT_LIST_HEAD(&card->ctl_files); +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + xa_init(&card->ctl_numids); + xa_init(&card->ctl_hash); +#endif + spin_lock_init(&card->files_lock); + INIT_LIST_HEAD(&card->files_list); + mutex_init(&card->memory_mutex); +#ifdef CONFIG_PM + init_waitqueue_head(&card->power_sleep); + init_waitqueue_head(&card->power_ref_sleep); + atomic_set(&card->power_ref, 0); +#endif + init_waitqueue_head(&card->remove_sleep); + card->sync_irq = -1; + + device_initialize(&card->card_dev); + card->card_dev.parent = parent; + card->card_dev.class = &sound_class; + card->card_dev.release = release_card_device; + card->card_dev.groups = card->dev_groups; + card->dev_groups[0] = &card_dev_attr_group; + err = kobject_set_name(&card->card_dev.kobj, "card%d", idx); + if (err < 0) + goto __error; + + snprintf(card->irq_descr, sizeof(card->irq_descr), "%s:%s", + dev_driver_string(card->dev), dev_name(&card->card_dev)); + + /* the control interface cannot be accessed from the user space until */ + /* snd_cards_bitmask and snd_cards are set with snd_card_register */ + err = snd_ctl_create(card); + if (err < 0) { + dev_err(parent, "unable to register control minors\n"); + goto __error; + } + err = snd_info_card_create(card); + if (err < 0) { + dev_err(parent, "unable to create card info\n"); + goto __error_ctl; + } + +#ifdef CONFIG_SND_DEBUG + card->debugfs_root = debugfs_create_dir(dev_name(&card->card_dev), + sound_debugfs_root); +#endif + return 0; + + __error_ctl: + snd_device_free_all(card); + __error: + put_device(&card->card_dev); + return err; +} + +/** + * snd_card_ref - Get the card object from the index + * @idx: the card index + * + * Returns a card object corresponding to the given index or NULL if not found. + * Release the object via snd_card_unref(). + * + * Return: a card object or NULL + */ +struct snd_card *snd_card_ref(int idx) +{ + struct snd_card *card; + + mutex_lock(&snd_card_mutex); + card = snd_cards[idx]; + if (card) + get_device(&card->card_dev); + mutex_unlock(&snd_card_mutex); + return card; +} +EXPORT_SYMBOL_GPL(snd_card_ref); + +/* return non-zero if a card is already locked */ +int snd_card_locked(int card) +{ + int locked; + + mutex_lock(&snd_card_mutex); + locked = test_bit(card, snd_cards_lock); + mutex_unlock(&snd_card_mutex); + return locked; +} + +static loff_t snd_disconnect_llseek(struct file *file, loff_t offset, int orig) +{ + return -ENODEV; +} + +static ssize_t snd_disconnect_read(struct file *file, char __user *buf, + size_t count, loff_t *offset) +{ + return -ENODEV; +} + +static ssize_t snd_disconnect_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + return -ENODEV; +} + +static int snd_disconnect_release(struct inode *inode, struct file *file) +{ + struct snd_monitor_file *df = NULL, *_df; + + spin_lock(&shutdown_lock); + list_for_each_entry(_df, &shutdown_files, shutdown_list) { + if (_df->file == file) { + df = _df; + list_del_init(&df->shutdown_list); + break; + } + } + spin_unlock(&shutdown_lock); + + if (likely(df)) { + if ((file->f_flags & FASYNC) && df->disconnected_f_op->fasync) + df->disconnected_f_op->fasync(-1, file, 0); + return df->disconnected_f_op->release(inode, file); + } + + panic("%s(%p, %p) failed!", __func__, inode, file); +} + +static __poll_t snd_disconnect_poll(struct file * file, poll_table * wait) +{ + return EPOLLERR | EPOLLNVAL; +} + +static long snd_disconnect_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return -ENODEV; +} + +static int snd_disconnect_mmap(struct file *file, struct vm_area_struct *vma) +{ + return -ENODEV; +} + +static int snd_disconnect_fasync(int fd, struct file *file, int on) +{ + return -ENODEV; +} + +static const struct file_operations snd_shutdown_f_ops = +{ + .owner = THIS_MODULE, + .llseek = snd_disconnect_llseek, + .read = snd_disconnect_read, + .write = snd_disconnect_write, + .release = snd_disconnect_release, + .poll = snd_disconnect_poll, + .unlocked_ioctl = snd_disconnect_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = snd_disconnect_ioctl, +#endif + .mmap = snd_disconnect_mmap, + .fasync = snd_disconnect_fasync +}; + +/** + * snd_card_disconnect - disconnect all APIs from the file-operations (user space) + * @card: soundcard structure + * + * Disconnects all APIs from the file-operations (user space). + * + * Return: Zero, otherwise a negative error code. + * + * Note: The current implementation replaces all active file->f_op with special + * dummy file operations (they do nothing except release). + */ +void snd_card_disconnect(struct snd_card *card) +{ + struct snd_monitor_file *mfile; + + if (!card) + return; + + spin_lock(&card->files_lock); + if (card->shutdown) { + spin_unlock(&card->files_lock); + return; + } + card->shutdown = 1; + + /* replace file->f_op with special dummy operations */ + list_for_each_entry(mfile, &card->files_list, list) { + /* it's critical part, use endless loop */ + /* we have no room to fail */ + mfile->disconnected_f_op = mfile->file->f_op; + + spin_lock(&shutdown_lock); + list_add(&mfile->shutdown_list, &shutdown_files); + spin_unlock(&shutdown_lock); + + mfile->file->f_op = &snd_shutdown_f_ops; + fops_get(mfile->file->f_op); + } + spin_unlock(&card->files_lock); + + /* notify all connected devices about disconnection */ + /* at this point, they cannot respond to any calls except release() */ + +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); +#endif + + /* notify all devices that we are disconnected */ + snd_device_disconnect_all(card); + + if (card->sync_irq > 0) + synchronize_irq(card->sync_irq); + + snd_info_card_disconnect(card); + if (card->registered) { + device_del(&card->card_dev); + card->registered = false; + } + + /* disable fops (user space) operations for ALSA API */ + mutex_lock(&snd_card_mutex); + snd_cards[card->number] = NULL; + clear_bit(card->number, snd_cards_lock); + mutex_unlock(&snd_card_mutex); + +#ifdef CONFIG_PM + wake_up(&card->power_sleep); + snd_power_sync_ref(card); +#endif +} +EXPORT_SYMBOL(snd_card_disconnect); + +/** + * snd_card_disconnect_sync - disconnect card and wait until files get closed + * @card: card object to disconnect + * + * This calls snd_card_disconnect() for disconnecting all belonging components + * and waits until all pending files get closed. + * It assures that all accesses from user-space finished so that the driver + * can release its resources gracefully. + */ +void snd_card_disconnect_sync(struct snd_card *card) +{ + snd_card_disconnect(card); + + spin_lock_irq(&card->files_lock); + wait_event_lock_irq(card->remove_sleep, + list_empty(&card->files_list), + card->files_lock); + spin_unlock_irq(&card->files_lock); +} +EXPORT_SYMBOL_GPL(snd_card_disconnect_sync); + +static int snd_card_do_free(struct snd_card *card) +{ + card->releasing = true; +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); +#endif + snd_device_free_all(card); + if (card->private_free) + card->private_free(card); + if (snd_info_card_free(card) < 0) { + dev_warn(card->dev, "unable to free card info\n"); + /* Not fatal error */ + } +#ifdef CONFIG_SND_DEBUG + debugfs_remove(card->debugfs_root); + card->debugfs_root = NULL; +#endif + if (card->release_completion) + complete(card->release_completion); + if (!card->managed) + kfree(card); + return 0; +} + +/** + * snd_card_free_when_closed - Disconnect the card, free it later eventually + * @card: soundcard structure + * + * Unlike snd_card_free(), this function doesn't try to release the card + * resource immediately, but tries to disconnect at first. When the card + * is still in use, the function returns before freeing the resources. + * The card resources will be freed when the refcount gets to zero. + * + * Return: zero if successful, or a negative error code + */ +void snd_card_free_when_closed(struct snd_card *card) +{ + if (!card) + return; + + snd_card_disconnect(card); + put_device(&card->card_dev); + return; +} +EXPORT_SYMBOL(snd_card_free_when_closed); + +/** + * snd_card_free - frees given soundcard structure + * @card: soundcard structure + * + * This function releases the soundcard structure and the all assigned + * devices automatically. That is, you don't have to release the devices + * by yourself. + * + * This function waits until the all resources are properly released. + * + * Return: Zero. Frees all associated devices and frees the control + * interface associated to given soundcard. + */ +void snd_card_free(struct snd_card *card) +{ + DECLARE_COMPLETION_ONSTACK(released); + + /* The call of snd_card_free() is allowed from various code paths; + * a manual call from the driver and the call via devres_free, and + * we need to avoid double-free. Moreover, the release via devres + * may call snd_card_free() twice due to its nature, we need to have + * the check here at the beginning. + */ + if (card->releasing) + return; + + card->release_completion = &released; + snd_card_free_when_closed(card); + + /* wait, until all devices are ready for the free operation */ + wait_for_completion(&released); +} +EXPORT_SYMBOL(snd_card_free); + +/* retrieve the last word of shortname or longname */ +static const char *retrieve_id_from_card_name(const char *name) +{ + const char *spos = name; + + while (*name) { + if (isspace(*name) && isalnum(name[1])) + spos = name + 1; + name++; + } + return spos; +} + +/* return true if the given id string doesn't conflict any other card ids */ +static bool card_id_ok(struct snd_card *card, const char *id) +{ + int i; + if (!snd_info_check_reserved_words(id)) + return false; + for (i = 0; i < snd_ecards_limit; i++) { + if (snd_cards[i] && snd_cards[i] != card && + !strcmp(snd_cards[i]->id, id)) + return false; + } + return true; +} + +/* copy to card->id only with valid letters from nid */ +static void copy_valid_id_string(struct snd_card *card, const char *src, + const char *nid) +{ + char *id = card->id; + + while (*nid && !isalnum(*nid)) + nid++; + if (isdigit(*nid)) + *id++ = isalpha(*src) ? *src : 'D'; + while (*nid && (size_t)(id - card->id) < sizeof(card->id) - 1) { + if (isalnum(*nid)) + *id++ = *nid; + nid++; + } + *id = 0; +} + +/* Set card->id from the given string + * If the string conflicts with other ids, add a suffix to make it unique. + */ +static void snd_card_set_id_no_lock(struct snd_card *card, const char *src, + const char *nid) +{ + int len, loops; + bool is_default = false; + char *id; + + copy_valid_id_string(card, src, nid); + id = card->id; + + again: + /* use "Default" for obviously invalid strings + * ("card" conflicts with proc directories) + */ + if (!*id || !strncmp(id, "card", 4)) { + strcpy(id, "Default"); + is_default = true; + } + + len = strlen(id); + for (loops = 0; loops < SNDRV_CARDS; loops++) { + char *spos; + char sfxstr[5]; /* "_012" */ + int sfxlen; + + if (card_id_ok(card, id)) + return; /* OK */ + + /* Add _XYZ suffix */ + sprintf(sfxstr, "_%X", loops + 1); + sfxlen = strlen(sfxstr); + if (len + sfxlen >= sizeof(card->id)) + spos = id + sizeof(card->id) - sfxlen - 1; + else + spos = id + len; + strcpy(spos, sfxstr); + } + /* fallback to the default id */ + if (!is_default) { + *id = 0; + goto again; + } + /* last resort... */ + dev_err(card->dev, "unable to set card id (%s)\n", id); + if (card->proc_root->name) + strscpy(card->id, card->proc_root->name, sizeof(card->id)); +} + +/** + * snd_card_set_id - set card identification name + * @card: soundcard structure + * @nid: new identification string + * + * This function sets the card identification and checks for name + * collisions. + */ +void snd_card_set_id(struct snd_card *card, const char *nid) +{ + /* check if user specified own card->id */ + if (card->id[0] != '\0') + return; + mutex_lock(&snd_card_mutex); + snd_card_set_id_no_lock(card, nid, nid); + mutex_unlock(&snd_card_mutex); +} +EXPORT_SYMBOL(snd_card_set_id); + +static ssize_t id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = container_of(dev, struct snd_card, card_dev); + return sysfs_emit(buf, "%s\n", card->id); +} + +static ssize_t id_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct snd_card *card = container_of(dev, struct snd_card, card_dev); + char buf1[sizeof(card->id)]; + size_t copy = count > sizeof(card->id) - 1 ? + sizeof(card->id) - 1 : count; + size_t idx; + int c; + + for (idx = 0; idx < copy; idx++) { + c = buf[idx]; + if (!isalnum(c) && c != '_' && c != '-') + return -EINVAL; + } + memcpy(buf1, buf, copy); + buf1[copy] = '\0'; + mutex_lock(&snd_card_mutex); + if (!card_id_ok(NULL, buf1)) { + mutex_unlock(&snd_card_mutex); + return -EEXIST; + } + strcpy(card->id, buf1); + snd_info_card_id_change(card); + mutex_unlock(&snd_card_mutex); + + return count; +} + +static DEVICE_ATTR_RW(id); + +static ssize_t number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_card *card = container_of(dev, struct snd_card, card_dev); + return sysfs_emit(buf, "%i\n", card->number); +} + +static DEVICE_ATTR_RO(number); + +static struct attribute *card_dev_attrs[] = { + &dev_attr_id.attr, + &dev_attr_number.attr, + NULL +}; + +static const struct attribute_group card_dev_attr_group = { + .attrs = card_dev_attrs, +}; + +/** + * snd_card_add_dev_attr - Append a new sysfs attribute group to card + * @card: card instance + * @group: attribute group to append + * + * Return: zero if successful, or a negative error code + */ +int snd_card_add_dev_attr(struct snd_card *card, + const struct attribute_group *group) +{ + int i; + + /* loop for (arraysize-1) here to keep NULL at the last entry */ + for (i = 0; i < ARRAY_SIZE(card->dev_groups) - 1; i++) { + if (!card->dev_groups[i]) { + card->dev_groups[i] = group; + return 0; + } + } + + dev_err(card->dev, "Too many groups assigned\n"); + return -ENOSPC; +} +EXPORT_SYMBOL_GPL(snd_card_add_dev_attr); + +static void trigger_card_free(void *data) +{ + snd_card_free(data); +} + +/** + * snd_card_register - register the soundcard + * @card: soundcard structure + * + * This function registers all the devices assigned to the soundcard. + * Until calling this, the ALSA control interface is blocked from the + * external accesses. Thus, you should call this function at the end + * of the initialization of the card. + * + * Return: Zero otherwise a negative error code if the registration failed. + */ +int snd_card_register(struct snd_card *card) +{ + int err; + + if (snd_BUG_ON(!card)) + return -EINVAL; + + if (!card->registered) { + err = device_add(&card->card_dev); + if (err < 0) + return err; + card->registered = true; + } else { + if (card->managed) + devm_remove_action(card->dev, trigger_card_free, card); + } + + if (card->managed) { + err = devm_add_action(card->dev, trigger_card_free, card); + if (err < 0) + return err; + } + + err = snd_device_register_all(card); + if (err < 0) + return err; + mutex_lock(&snd_card_mutex); + if (snd_cards[card->number]) { + /* already registered */ + mutex_unlock(&snd_card_mutex); + return snd_info_card_register(card); /* register pending info */ + } + if (*card->id) { + /* make a unique id name from the given string */ + char tmpid[sizeof(card->id)]; + memcpy(tmpid, card->id, sizeof(card->id)); + snd_card_set_id_no_lock(card, tmpid, tmpid); + } else { + /* create an id from either shortname or longname */ + const char *src; + src = *card->shortname ? card->shortname : card->longname; + snd_card_set_id_no_lock(card, src, + retrieve_id_from_card_name(src)); + } + snd_cards[card->number] = card; + mutex_unlock(&snd_card_mutex); + err = snd_info_card_register(card); + if (err < 0) + return err; + +#if IS_ENABLED(CONFIG_SND_MIXER_OSS) + if (snd_mixer_oss_notify_callback) + snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); +#endif + return 0; +} +EXPORT_SYMBOL(snd_card_register); + +#ifdef CONFIG_SND_PROC_FS +static void snd_card_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int idx, count; + struct snd_card *card; + + for (idx = count = 0; idx < SNDRV_CARDS; idx++) { + mutex_lock(&snd_card_mutex); + card = snd_cards[idx]; + if (card) { + count++; + snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n", + idx, + card->id, + card->driver, + card->shortname); + snd_iprintf(buffer, " %s\n", + card->longname); + } + mutex_unlock(&snd_card_mutex); + } + if (!count) + snd_iprintf(buffer, "--- no soundcards ---\n"); +} + +#ifdef CONFIG_SND_OSSEMUL +void snd_card_info_read_oss(struct snd_info_buffer *buffer) +{ + int idx, count; + struct snd_card *card; + + for (idx = count = 0; idx < SNDRV_CARDS; idx++) { + mutex_lock(&snd_card_mutex); + card = snd_cards[idx]; + if (card) { + count++; + snd_iprintf(buffer, "%s\n", card->longname); + } + mutex_unlock(&snd_card_mutex); + } + if (!count) { + snd_iprintf(buffer, "--- no soundcards ---\n"); + } +} + +#endif + +#ifdef MODULE +static void snd_card_module_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int idx; + struct snd_card *card; + + for (idx = 0; idx < SNDRV_CARDS; idx++) { + mutex_lock(&snd_card_mutex); + card = snd_cards[idx]; + if (card) + snd_iprintf(buffer, "%2i %s\n", + idx, card->module->name); + mutex_unlock(&snd_card_mutex); + } +} +#endif + +int __init snd_card_info_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL); + if (! entry) + return -ENOMEM; + entry->c.text.read = snd_card_info_read; + if (snd_info_register(entry) < 0) + return -ENOMEM; /* freed in error path */ + +#ifdef MODULE + entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_card_module_info_read; + if (snd_info_register(entry) < 0) + return -ENOMEM; /* freed in error path */ +#endif + + return 0; +} +#endif /* CONFIG_SND_PROC_FS */ + +/** + * snd_component_add - add a component string + * @card: soundcard structure + * @component: the component id string + * + * This function adds the component id string to the supported list. + * The component can be referred from the alsa-lib. + * + * Return: Zero otherwise a negative error code. + */ + +int snd_component_add(struct snd_card *card, const char *component) +{ + char *ptr; + int len = strlen(component); + + ptr = strstr(card->components, component); + if (ptr != NULL) { + if (ptr[len] == '\0' || ptr[len] == ' ') /* already there */ + return 1; + } + if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { + snd_BUG(); + return -ENOMEM; + } + if (card->components[0] != '\0') + strcat(card->components, " "); + strcat(card->components, component); + return 0; +} +EXPORT_SYMBOL(snd_component_add); + +/** + * snd_card_file_add - add the file to the file list of the card + * @card: soundcard structure + * @file: file pointer + * + * This function adds the file to the file linked-list of the card. + * This linked-list is used to keep tracking the connection state, + * and to avoid the release of busy resources by hotplug. + * + * Return: zero or a negative error code. + */ +int snd_card_file_add(struct snd_card *card, struct file *file) +{ + struct snd_monitor_file *mfile; + + mfile = kmalloc(sizeof(*mfile), GFP_KERNEL); + if (mfile == NULL) + return -ENOMEM; + mfile->file = file; + mfile->disconnected_f_op = NULL; + INIT_LIST_HEAD(&mfile->shutdown_list); + spin_lock(&card->files_lock); + if (card->shutdown) { + spin_unlock(&card->files_lock); + kfree(mfile); + return -ENODEV; + } + list_add(&mfile->list, &card->files_list); + get_device(&card->card_dev); + spin_unlock(&card->files_lock); + return 0; +} +EXPORT_SYMBOL(snd_card_file_add); + +/** + * snd_card_file_remove - remove the file from the file list + * @card: soundcard structure + * @file: file pointer + * + * This function removes the file formerly added to the card via + * snd_card_file_add() function. + * If all files are removed and snd_card_free_when_closed() was + * called beforehand, it processes the pending release of + * resources. + * + * Return: Zero or a negative error code. + */ +int snd_card_file_remove(struct snd_card *card, struct file *file) +{ + struct snd_monitor_file *mfile, *found = NULL; + + spin_lock(&card->files_lock); + list_for_each_entry(mfile, &card->files_list, list) { + if (mfile->file == file) { + list_del(&mfile->list); + spin_lock(&shutdown_lock); + list_del(&mfile->shutdown_list); + spin_unlock(&shutdown_lock); + if (mfile->disconnected_f_op) + fops_put(mfile->disconnected_f_op); + found = mfile; + break; + } + } + if (list_empty(&card->files_list)) + wake_up_all(&card->remove_sleep); + spin_unlock(&card->files_lock); + if (!found) { + dev_err(card->dev, "card file remove problem (%p)\n", file); + return -ENOENT; + } + kfree(found); + put_device(&card->card_dev); + return 0; +} +EXPORT_SYMBOL(snd_card_file_remove); + +#ifdef CONFIG_PM +/** + * snd_power_ref_and_wait - wait until the card gets powered up + * @card: soundcard structure + * + * Take the power_ref reference count of the given card, and + * wait until the card gets powered up to SNDRV_CTL_POWER_D0 state. + * The refcount is down again while sleeping until power-up, hence this + * function can be used for syncing the floating control ops accesses, + * typically around calling control ops. + * + * The caller needs to pull down the refcount via snd_power_unref() later + * no matter whether the error is returned from this function or not. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_power_ref_and_wait(struct snd_card *card) +{ + snd_power_ref(card); + if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0) + return 0; + wait_event_cmd(card->power_sleep, + card->shutdown || + snd_power_get_state(card) == SNDRV_CTL_POWER_D0, + snd_power_unref(card), snd_power_ref(card)); + return card->shutdown ? -ENODEV : 0; +} +EXPORT_SYMBOL_GPL(snd_power_ref_and_wait); + +/** + * snd_power_wait - wait until the card gets powered up (old form) + * @card: soundcard structure + * + * Wait until the card gets powered up to SNDRV_CTL_POWER_D0 state. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_power_wait(struct snd_card *card) +{ + int ret; + + ret = snd_power_ref_and_wait(card); + snd_power_unref(card); + return ret; +} +EXPORT_SYMBOL(snd_power_wait); +#endif /* CONFIG_PM */ diff --git a/sound/core/isadma.c b/sound/core/isadma.c new file mode 100644 index 0000000000..28768061d7 --- /dev/null +++ b/sound/core/isadma.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISA DMA support functions + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +/* + * Defining following add some delay. Maybe this helps for some broken + * ISA DMA controllers. + */ + +#undef HAVE_REALLY_SLOW_DMA_CONTROLLER + +#include <linux/export.h> +#include <linux/isa-dma.h> +#include <sound/core.h> + +/** + * snd_dma_program - program an ISA DMA transfer + * @dma: the dma number + * @addr: the physical address of the buffer + * @size: the DMA transfer size + * @mode: the DMA transfer mode, DMA_MODE_XXX + * + * Programs an ISA DMA transfer for the given buffer. + */ +void snd_dma_program(unsigned long dma, + unsigned long addr, unsigned int size, + unsigned short mode) +{ + unsigned long flags; + + flags = claim_dma_lock(); + disable_dma(dma); + clear_dma_ff(dma); + set_dma_mode(dma, mode); + set_dma_addr(dma, addr); + set_dma_count(dma, size); + if (!(mode & DMA_MODE_NO_ENABLE)) + enable_dma(dma); + release_dma_lock(flags); +} +EXPORT_SYMBOL(snd_dma_program); + +/** + * snd_dma_disable - stop the ISA DMA transfer + * @dma: the dma number + * + * Stops the ISA DMA transfer. + */ +void snd_dma_disable(unsigned long dma) +{ + unsigned long flags; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + disable_dma(dma); + release_dma_lock(flags); +} +EXPORT_SYMBOL(snd_dma_disable); + +/** + * snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes + * @dma: the dma number + * @size: the dma transfer size + * + * Return: The current pointer in DMA transfer buffer in bytes. + */ +unsigned int snd_dma_pointer(unsigned long dma, unsigned int size) +{ + unsigned long flags; + unsigned int result, result1; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + if (!isa_dma_bridge_buggy) + disable_dma(dma); + result = get_dma_residue(dma); + /* + * HACK - read the counter again and choose higher value in order to + * avoid reading during counter lower byte roll over if the + * isa_dma_bridge_buggy is set. + */ + result1 = get_dma_residue(dma); + if (!isa_dma_bridge_buggy) + enable_dma(dma); + release_dma_lock(flags); + if (unlikely(result < result1)) + result = result1; +#ifdef CONFIG_SND_DEBUG + if (result > size) + pr_err("ALSA: pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size); +#endif + if (result >= size || result == 0) + return 0; + else + return size - result; +} +EXPORT_SYMBOL(snd_dma_pointer); + +struct snd_dma_data { + int dma; +}; + +static void __snd_release_dma(struct device *dev, void *data) +{ + struct snd_dma_data *p = data; + + snd_dma_disable(p->dma); + free_dma(p->dma); +} + +/** + * snd_devm_request_dma - the managed version of request_dma() + * @dev: the device pointer + * @dma: the dma number + * @name: the name string of the requester + * + * The requested DMA will be automatically released at unbinding via devres. + * + * Return: zero on success, or a negative error code + */ +int snd_devm_request_dma(struct device *dev, int dma, const char *name) +{ + struct snd_dma_data *p; + + if (request_dma(dma, name)) + return -EBUSY; + p = devres_alloc(__snd_release_dma, sizeof(*p), GFP_KERNEL); + if (!p) { + free_dma(dma); + return -ENOMEM; + } + p->dma = dma; + devres_add(dev, p); + return 0; +} +EXPORT_SYMBOL_GPL(snd_devm_request_dma); diff --git a/sound/core/jack.c b/sound/core/jack.c new file mode 100644 index 0000000000..e0f034e727 --- /dev/null +++ b/sound/core/jack.c @@ -0,0 +1,697 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Jack abstraction layer + * + * Copyright 2008 Wolfson Microelectronics + */ + +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/ctype.h> +#include <linux/mm.h> +#include <linux/debugfs.h> +#include <sound/jack.h> +#include <sound/core.h> +#include <sound/control.h> + +struct snd_jack_kctl { + struct snd_kcontrol *kctl; + struct list_head list; /* list of controls belong to the same jack */ + unsigned int mask_bits; /* only masked status bits are reported via kctl */ + struct snd_jack *jack; /* pointer to struct snd_jack */ + bool sw_inject_enable; /* allow to inject plug event via debugfs */ +#ifdef CONFIG_SND_JACK_INJECTION_DEBUG + struct dentry *jack_debugfs_root; /* jack_kctl debugfs root */ +#endif +}; + +#ifdef CONFIG_SND_JACK_INPUT_DEV +static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = { + SW_HEADPHONE_INSERT, + SW_MICROPHONE_INSERT, + SW_LINEOUT_INSERT, + SW_JACK_PHYSICAL_INSERT, + SW_VIDEOOUT_INSERT, + SW_LINEIN_INSERT, +}; +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + +static int snd_jack_dev_disconnect(struct snd_device *device) +{ +#ifdef CONFIG_SND_JACK_INPUT_DEV + struct snd_jack *jack = device->device_data; + + mutex_lock(&jack->input_dev_lock); + if (!jack->input_dev) { + mutex_unlock(&jack->input_dev_lock); + return 0; + } + + /* If the input device is registered with the input subsystem + * then we need to use a different deallocator. */ + if (jack->registered) + input_unregister_device(jack->input_dev); + else + input_free_device(jack->input_dev); + jack->input_dev = NULL; + mutex_unlock(&jack->input_dev_lock); +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + return 0; +} + +static int snd_jack_dev_free(struct snd_device *device) +{ + struct snd_jack *jack = device->device_data; + struct snd_card *card = device->card; + struct snd_jack_kctl *jack_kctl, *tmp_jack_kctl; + + list_for_each_entry_safe(jack_kctl, tmp_jack_kctl, &jack->kctl_list, list) { + list_del_init(&jack_kctl->list); + snd_ctl_remove(card, jack_kctl->kctl); + } + + if (jack->private_free) + jack->private_free(jack); + + snd_jack_dev_disconnect(device); + + kfree(jack->id); + kfree(jack); + + return 0; +} + +#ifdef CONFIG_SND_JACK_INPUT_DEV +static int snd_jack_dev_register(struct snd_device *device) +{ + struct snd_jack *jack = device->device_data; + struct snd_card *card = device->card; + int err, i; + + snprintf(jack->name, sizeof(jack->name), "%s %s", + card->shortname, jack->id); + + mutex_lock(&jack->input_dev_lock); + if (!jack->input_dev) { + mutex_unlock(&jack->input_dev_lock); + return 0; + } + + jack->input_dev->name = jack->name; + + /* Default to the sound card device. */ + if (!jack->input_dev->dev.parent) + jack->input_dev->dev.parent = snd_card_get_device_link(card); + + /* Add capabilities for any keys that are enabled */ + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = SND_JACK_BTN_0 >> i; + + if (!(jack->type & testbit)) + continue; + + if (!jack->key[i]) + jack->key[i] = BTN_0 + i; + + input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); + } + + err = input_register_device(jack->input_dev); + if (err == 0) + jack->registered = 1; + + mutex_unlock(&jack->input_dev_lock); + return err; +} +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + +#ifdef CONFIG_SND_JACK_INJECTION_DEBUG +static void snd_jack_inject_report(struct snd_jack_kctl *jack_kctl, int status) +{ + struct snd_jack *jack; +#ifdef CONFIG_SND_JACK_INPUT_DEV + int i; +#endif + if (!jack_kctl) + return; + + jack = jack_kctl->jack; + + if (jack_kctl->sw_inject_enable) + snd_kctl_jack_report(jack->card, jack_kctl->kctl, + status & jack_kctl->mask_bits); + +#ifdef CONFIG_SND_JACK_INPUT_DEV + if (!jack->input_dev) + return; + + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = ((SND_JACK_BTN_0 >> i) & jack_kctl->mask_bits); + + if (jack->type & testbit) + input_report_key(jack->input_dev, jack->key[i], + status & testbit); + } + + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) { + int testbit = ((1 << i) & jack_kctl->mask_bits); + + if (jack->type & testbit) + input_report_switch(jack->input_dev, + jack_switch_types[i], + status & testbit); + } + + input_sync(jack->input_dev); +#endif /* CONFIG_SND_JACK_INPUT_DEV */ +} + +static ssize_t sw_inject_enable_read(struct file *file, + char __user *to, size_t count, loff_t *ppos) +{ + struct snd_jack_kctl *jack_kctl = file->private_data; + int len, ret; + char buf[128]; + + len = scnprintf(buf, sizeof(buf), "%s: %s\t\t%s: %i\n", "Jack", jack_kctl->kctl->id.name, + "Inject Enabled", jack_kctl->sw_inject_enable); + ret = simple_read_from_buffer(to, count, ppos, buf, len); + + return ret; +} + +static ssize_t sw_inject_enable_write(struct file *file, + const char __user *from, size_t count, loff_t *ppos) +{ + struct snd_jack_kctl *jack_kctl = file->private_data; + int ret, err; + unsigned long enable; + char buf[8] = { 0 }; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, from, count); + err = kstrtoul(buf, 0, &enable); + if (err) + return err; + + if (jack_kctl->sw_inject_enable == (!!enable)) + return ret; + + jack_kctl->sw_inject_enable = !!enable; + + if (!jack_kctl->sw_inject_enable) + snd_jack_report(jack_kctl->jack, jack_kctl->jack->hw_status_cache); + + return ret; +} + +static ssize_t jackin_inject_write(struct file *file, + const char __user *from, size_t count, loff_t *ppos) +{ + struct snd_jack_kctl *jack_kctl = file->private_data; + int ret, err; + unsigned long enable; + char buf[8] = { 0 }; + + if (!jack_kctl->sw_inject_enable) + return -EINVAL; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, from, count); + err = kstrtoul(buf, 0, &enable); + if (err) + return err; + + snd_jack_inject_report(jack_kctl, !!enable ? jack_kctl->mask_bits : 0); + + return ret; +} + +static ssize_t jack_kctl_id_read(struct file *file, + char __user *to, size_t count, loff_t *ppos) +{ + struct snd_jack_kctl *jack_kctl = file->private_data; + char buf[64]; + int len, ret; + + len = scnprintf(buf, sizeof(buf), "%s\n", jack_kctl->kctl->id.name); + ret = simple_read_from_buffer(to, count, ppos, buf, len); + + return ret; +} + +/* the bit definition is aligned with snd_jack_types in jack.h */ +static const char * const jack_events_name[] = { + "HEADPHONE(0x0001)", "MICROPHONE(0x0002)", "LINEOUT(0x0004)", + "MECHANICAL(0x0008)", "VIDEOOUT(0x0010)", "LINEIN(0x0020)", + "", "", "", "BTN_5(0x0200)", "BTN_4(0x0400)", "BTN_3(0x0800)", + "BTN_2(0x1000)", "BTN_1(0x2000)", "BTN_0(0x4000)", "", +}; + +/* the recommended buffer size is 256 */ +static int parse_mask_bits(unsigned int mask_bits, char *buf, size_t buf_size) +{ + int i; + + scnprintf(buf, buf_size, "0x%04x", mask_bits); + + for (i = 0; i < ARRAY_SIZE(jack_events_name); i++) + if (mask_bits & (1 << i)) { + strlcat(buf, " ", buf_size); + strlcat(buf, jack_events_name[i], buf_size); + } + strlcat(buf, "\n", buf_size); + + return strlen(buf); +} + +static ssize_t jack_kctl_mask_bits_read(struct file *file, + char __user *to, size_t count, loff_t *ppos) +{ + struct snd_jack_kctl *jack_kctl = file->private_data; + char buf[256]; + int len, ret; + + len = parse_mask_bits(jack_kctl->mask_bits, buf, sizeof(buf)); + ret = simple_read_from_buffer(to, count, ppos, buf, len); + + return ret; +} + +static ssize_t jack_kctl_status_read(struct file *file, + char __user *to, size_t count, loff_t *ppos) +{ + struct snd_jack_kctl *jack_kctl = file->private_data; + char buf[16]; + int len, ret; + + len = scnprintf(buf, sizeof(buf), "%s\n", jack_kctl->kctl->private_value ? + "Plugged" : "Unplugged"); + ret = simple_read_from_buffer(to, count, ppos, buf, len); + + return ret; +} + +#ifdef CONFIG_SND_JACK_INPUT_DEV +static ssize_t jack_type_read(struct file *file, + char __user *to, size_t count, loff_t *ppos) +{ + struct snd_jack_kctl *jack_kctl = file->private_data; + char buf[256]; + int len, ret; + + len = parse_mask_bits(jack_kctl->jack->type, buf, sizeof(buf)); + ret = simple_read_from_buffer(to, count, ppos, buf, len); + + return ret; +} + +static const struct file_operations jack_type_fops = { + .open = simple_open, + .read = jack_type_read, + .llseek = default_llseek, +}; +#endif + +static const struct file_operations sw_inject_enable_fops = { + .open = simple_open, + .read = sw_inject_enable_read, + .write = sw_inject_enable_write, + .llseek = default_llseek, +}; + +static const struct file_operations jackin_inject_fops = { + .open = simple_open, + .write = jackin_inject_write, + .llseek = default_llseek, +}; + +static const struct file_operations jack_kctl_id_fops = { + .open = simple_open, + .read = jack_kctl_id_read, + .llseek = default_llseek, +}; + +static const struct file_operations jack_kctl_mask_bits_fops = { + .open = simple_open, + .read = jack_kctl_mask_bits_read, + .llseek = default_llseek, +}; + +static const struct file_operations jack_kctl_status_fops = { + .open = simple_open, + .read = jack_kctl_status_read, + .llseek = default_llseek, +}; + +static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, + struct snd_jack_kctl *jack_kctl) +{ + char *tname; + int i; + + /* Don't create injection interface for Phantom jacks */ + if (strstr(jack_kctl->kctl->id.name, "Phantom")) + return 0; + + tname = kstrdup(jack_kctl->kctl->id.name, GFP_KERNEL); + if (!tname) + return -ENOMEM; + + /* replace the chars which are not suitable for folder's name with _ */ + for (i = 0; tname[i]; i++) + if (!isalnum(tname[i])) + tname[i] = '_'; + + jack_kctl->jack_debugfs_root = debugfs_create_dir(tname, jack->card->debugfs_root); + kfree(tname); + + debugfs_create_file("sw_inject_enable", 0644, jack_kctl->jack_debugfs_root, jack_kctl, + &sw_inject_enable_fops); + + debugfs_create_file("jackin_inject", 0200, jack_kctl->jack_debugfs_root, jack_kctl, + &jackin_inject_fops); + + debugfs_create_file("kctl_id", 0444, jack_kctl->jack_debugfs_root, jack_kctl, + &jack_kctl_id_fops); + + debugfs_create_file("mask_bits", 0444, jack_kctl->jack_debugfs_root, jack_kctl, + &jack_kctl_mask_bits_fops); + + debugfs_create_file("status", 0444, jack_kctl->jack_debugfs_root, jack_kctl, + &jack_kctl_status_fops); + +#ifdef CONFIG_SND_JACK_INPUT_DEV + debugfs_create_file("type", 0444, jack_kctl->jack_debugfs_root, jack_kctl, + &jack_type_fops); +#endif + return 0; +} + +static void snd_jack_debugfs_clear_inject_node(struct snd_jack_kctl *jack_kctl) +{ + debugfs_remove(jack_kctl->jack_debugfs_root); + jack_kctl->jack_debugfs_root = NULL; +} +#else /* CONFIG_SND_JACK_INJECTION_DEBUG */ +static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, + struct snd_jack_kctl *jack_kctl) +{ + return 0; +} + +static void snd_jack_debugfs_clear_inject_node(struct snd_jack_kctl *jack_kctl) +{ +} +#endif /* CONFIG_SND_JACK_INJECTION_DEBUG */ + +static void snd_jack_kctl_private_free(struct snd_kcontrol *kctl) +{ + struct snd_jack_kctl *jack_kctl; + + jack_kctl = kctl->private_data; + if (jack_kctl) { + snd_jack_debugfs_clear_inject_node(jack_kctl); + list_del(&jack_kctl->list); + kfree(jack_kctl); + } +} + +static void snd_jack_kctl_add(struct snd_jack *jack, struct snd_jack_kctl *jack_kctl) +{ + jack_kctl->jack = jack; + list_add_tail(&jack_kctl->list, &jack->kctl_list); + snd_jack_debugfs_add_inject_node(jack, jack_kctl); +} + +static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const char *name, unsigned int mask) +{ + struct snd_kcontrol *kctl; + struct snd_jack_kctl *jack_kctl; + int err; + + kctl = snd_kctl_jack_new(name, card); + if (!kctl) + return NULL; + + err = snd_ctl_add(card, kctl); + if (err < 0) + return NULL; + + jack_kctl = kzalloc(sizeof(*jack_kctl), GFP_KERNEL); + + if (!jack_kctl) + goto error; + + jack_kctl->kctl = kctl; + jack_kctl->mask_bits = mask; + + kctl->private_data = jack_kctl; + kctl->private_free = snd_jack_kctl_private_free; + + return jack_kctl; +error: + snd_ctl_free_one(kctl); + return NULL; +} + +/** + * snd_jack_add_new_kctl - Create a new snd_jack_kctl and add it to jack + * @jack: the jack instance which the kctl will attaching to + * @name: the name for the snd_kcontrol object + * @mask: a bitmask of enum snd_jack_type values that can be detected + * by this snd_jack_kctl object. + * + * Creates a new snd_kcontrol object and adds it to the jack kctl_list. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_jack_add_new_kctl(struct snd_jack *jack, const char * name, int mask) +{ + struct snd_jack_kctl *jack_kctl; + + jack_kctl = snd_jack_kctl_new(jack->card, name, mask); + if (!jack_kctl) + return -ENOMEM; + + snd_jack_kctl_add(jack, jack_kctl); + return 0; +} +EXPORT_SYMBOL(snd_jack_add_new_kctl); + +/** + * snd_jack_new - Create a new jack + * @card: the card instance + * @id: an identifying string for this jack + * @type: a bitmask of enum snd_jack_type values that can be detected by + * this jack + * @jjack: Used to provide the allocated jack object to the caller. + * @initial_kctl: if true, create a kcontrol and add it to the jack list. + * @phantom_jack: Don't create a input device for phantom jacks. + * + * Creates a new jack object. + * + * Return: Zero if successful, or a negative error code on failure. + * On success @jjack will be initialised. + */ +int snd_jack_new(struct snd_card *card, const char *id, int type, + struct snd_jack **jjack, bool initial_kctl, bool phantom_jack) +{ + struct snd_jack *jack; + struct snd_jack_kctl *jack_kctl = NULL; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_jack_dev_free, +#ifdef CONFIG_SND_JACK_INPUT_DEV + .dev_register = snd_jack_dev_register, + .dev_disconnect = snd_jack_dev_disconnect, +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + }; + + if (initial_kctl) { + jack_kctl = snd_jack_kctl_new(card, id, type); + if (!jack_kctl) + return -ENOMEM; + } + + jack = kzalloc(sizeof(struct snd_jack), GFP_KERNEL); + if (jack == NULL) + return -ENOMEM; + + jack->id = kstrdup(id, GFP_KERNEL); + if (jack->id == NULL) { + kfree(jack); + return -ENOMEM; + } + +#ifdef CONFIG_SND_JACK_INPUT_DEV + mutex_init(&jack->input_dev_lock); + + /* don't create input device for phantom jack */ + if (!phantom_jack) { + int i; + + jack->input_dev = input_allocate_device(); + if (jack->input_dev == NULL) { + err = -ENOMEM; + goto fail_input; + } + + jack->input_dev->phys = "ALSA"; + + jack->type = type; + + for (i = 0; i < SND_JACK_SWITCH_TYPES; i++) + if (type & (1 << i)) + input_set_capability(jack->input_dev, EV_SW, + jack_switch_types[i]); + + } +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + + err = snd_device_new(card, SNDRV_DEV_JACK, jack, &ops); + if (err < 0) + goto fail_input; + + jack->card = card; + INIT_LIST_HEAD(&jack->kctl_list); + + if (initial_kctl) + snd_jack_kctl_add(jack, jack_kctl); + + *jjack = jack; + + return 0; + +fail_input: +#ifdef CONFIG_SND_JACK_INPUT_DEV + input_free_device(jack->input_dev); +#endif + kfree(jack->id); + kfree(jack); + return err; +} +EXPORT_SYMBOL(snd_jack_new); + +#ifdef CONFIG_SND_JACK_INPUT_DEV +/** + * snd_jack_set_parent - Set the parent device for a jack + * + * @jack: The jack to configure + * @parent: The device to set as parent for the jack. + * + * Set the parent for the jack devices in the device tree. This + * function is only valid prior to registration of the jack. If no + * parent is configured then the parent device will be the sound card. + */ +void snd_jack_set_parent(struct snd_jack *jack, struct device *parent) +{ + WARN_ON(jack->registered); + mutex_lock(&jack->input_dev_lock); + if (!jack->input_dev) { + mutex_unlock(&jack->input_dev_lock); + return; + } + + jack->input_dev->dev.parent = parent; + mutex_unlock(&jack->input_dev_lock); +} +EXPORT_SYMBOL(snd_jack_set_parent); + +/** + * snd_jack_set_key - Set a key mapping on a jack + * + * @jack: The jack to configure + * @type: Jack report type for this key + * @keytype: Input layer key type to be reported + * + * Map a SND_JACK_BTN_* button type to an input layer key, allowing + * reporting of keys on accessories via the jack abstraction. If no + * mapping is provided but keys are enabled in the jack type then + * BTN_n numeric buttons will be reported. + * + * If jacks are not reporting via the input API this call will have no + * effect. + * + * Note that this is intended to be use by simple devices with small + * numbers of keys that can be reported. It is also possible to + * access the input device directly - devices with complex input + * capabilities on accessories should consider doing this rather than + * using this abstraction. + * + * This function may only be called prior to registration of the jack. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_jack_set_key(struct snd_jack *jack, enum snd_jack_types type, + int keytype) +{ + int key = fls(SND_JACK_BTN_0) - fls(type); + + WARN_ON(jack->registered); + + if (!keytype || key >= ARRAY_SIZE(jack->key)) + return -EINVAL; + + jack->type |= type; + jack->key[key] = keytype; + return 0; +} +EXPORT_SYMBOL(snd_jack_set_key); +#endif /* CONFIG_SND_JACK_INPUT_DEV */ + +/** + * snd_jack_report - Report the current status of a jack + * Note: This function uses mutexes and should be called from a + * context which can sleep (such as a workqueue). + * + * @jack: The jack to report status for + * @status: The current status of the jack + */ +void snd_jack_report(struct snd_jack *jack, int status) +{ + struct snd_jack_kctl *jack_kctl; + unsigned int mask_bits = 0; +#ifdef CONFIG_SND_JACK_INPUT_DEV + struct input_dev *idev; + int i; +#endif + + if (!jack) + return; + + jack->hw_status_cache = status; + + list_for_each_entry(jack_kctl, &jack->kctl_list, list) + if (jack_kctl->sw_inject_enable) + mask_bits |= jack_kctl->mask_bits; + else + snd_kctl_jack_report(jack->card, jack_kctl->kctl, + status & jack_kctl->mask_bits); + +#ifdef CONFIG_SND_JACK_INPUT_DEV + idev = input_get_device(jack->input_dev); + if (!idev) + return; + + for (i = 0; i < ARRAY_SIZE(jack->key); i++) { + int testbit = ((SND_JACK_BTN_0 >> i) & ~mask_bits); + + if (jack->type & testbit) + input_report_key(idev, jack->key[i], + status & testbit); + } + + for (i = 0; i < ARRAY_SIZE(jack_switch_types); i++) { + int testbit = ((1 << i) & ~mask_bits); + + if (jack->type & testbit) + input_report_switch(idev, + jack_switch_types[i], + status & testbit); + } + + input_sync(idev); + input_put_device(idev); +#endif /* CONFIG_SND_JACK_INPUT_DEV */ +} +EXPORT_SYMBOL(snd_jack_report); diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c new file mode 100644 index 0000000000..f901504b5a --- /dev/null +++ b/sound/core/memalloc.c @@ -0,0 +1,950 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Takashi Iwai <tiwai@suse.de> + * + * Generic memory allocators + */ + +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/dma-map-ops.h> +#include <linux/genalloc.h> +#include <linux/highmem.h> +#include <linux/vmalloc.h> +#ifdef CONFIG_X86 +#include <asm/set_memory.h> +#endif +#include <sound/memalloc.h> +#include "memalloc_local.h" + +#define DEFAULT_GFP \ + (GFP_KERNEL | \ + __GFP_RETRY_MAYFAIL | /* don't trigger OOM-killer */ \ + __GFP_NOWARN) /* no stack trace print - this call is non-critical */ + +static const struct snd_malloc_ops *snd_dma_get_ops(struct snd_dma_buffer *dmab); + +#ifdef CONFIG_SND_DMA_SGBUF +static void *snd_dma_sg_fallback_alloc(struct snd_dma_buffer *dmab, size_t size); +#endif + +static void *__snd_dma_alloc_pages(struct snd_dma_buffer *dmab, size_t size) +{ + const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); + + if (WARN_ON_ONCE(!ops || !ops->alloc)) + return NULL; + return ops->alloc(dmab, size); +} + +/** + * snd_dma_alloc_dir_pages - allocate the buffer area according to the given + * type and direction + * @type: the DMA buffer type + * @device: the device pointer + * @dir: DMA direction + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. + * + * Return: Zero if the buffer with the given size is allocated successfully, + * otherwise a negative value on error. + */ +int snd_dma_alloc_dir_pages(int type, struct device *device, + enum dma_data_direction dir, size_t size, + struct snd_dma_buffer *dmab) +{ + if (WARN_ON(!size)) + return -ENXIO; + if (WARN_ON(!dmab)) + return -ENXIO; + + size = PAGE_ALIGN(size); + dmab->dev.type = type; + dmab->dev.dev = device; + dmab->dev.dir = dir; + dmab->bytes = 0; + dmab->addr = 0; + dmab->private_data = NULL; + dmab->area = __snd_dma_alloc_pages(dmab, size); + if (!dmab->area) + return -ENOMEM; + dmab->bytes = size; + return 0; +} +EXPORT_SYMBOL(snd_dma_alloc_dir_pages); + +/** + * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback + * @type: the DMA buffer type + * @device: the device pointer + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. When no space is left, this function reduces the size and + * tries to allocate again. The size actually allocated is stored in + * res_size argument. + * + * Return: Zero if the buffer with the given size is allocated successfully, + * otherwise a negative value on error. + */ +int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size, + struct snd_dma_buffer *dmab) +{ + int err; + + while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) { + if (err != -ENOMEM) + return err; + if (size <= PAGE_SIZE) + return -ENOMEM; + size >>= 1; + size = PAGE_SIZE << get_order(size); + } + if (! dmab->area) + return -ENOMEM; + return 0; +} +EXPORT_SYMBOL(snd_dma_alloc_pages_fallback); + +/** + * snd_dma_free_pages - release the allocated buffer + * @dmab: the buffer allocation record to release + * + * Releases the allocated buffer via snd_dma_alloc_pages(). + */ +void snd_dma_free_pages(struct snd_dma_buffer *dmab) +{ + const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); + + if (ops && ops->free) + ops->free(dmab); +} +EXPORT_SYMBOL(snd_dma_free_pages); + +/* called by devres */ +static void __snd_release_pages(struct device *dev, void *res) +{ + snd_dma_free_pages(res); +} + +/** + * snd_devm_alloc_dir_pages - allocate the buffer and manage with devres + * @dev: the device pointer + * @type: the DMA buffer type + * @dir: DMA direction + * @size: the buffer size to allocate + * + * Allocate buffer pages depending on the given type and manage using devres. + * The pages will be released automatically at the device removal. + * + * Unlike snd_dma_alloc_pages(), this function requires the real device pointer, + * hence it can't work with SNDRV_DMA_TYPE_CONTINUOUS or + * SNDRV_DMA_TYPE_VMALLOC type. + * + * Return: the snd_dma_buffer object at success, or NULL if failed + */ +struct snd_dma_buffer * +snd_devm_alloc_dir_pages(struct device *dev, int type, + enum dma_data_direction dir, size_t size) +{ + struct snd_dma_buffer *dmab; + int err; + + if (WARN_ON(type == SNDRV_DMA_TYPE_CONTINUOUS || + type == SNDRV_DMA_TYPE_VMALLOC)) + return NULL; + + dmab = devres_alloc(__snd_release_pages, sizeof(*dmab), GFP_KERNEL); + if (!dmab) + return NULL; + + err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab); + if (err < 0) { + devres_free(dmab); + return NULL; + } + + devres_add(dev, dmab); + return dmab; +} +EXPORT_SYMBOL_GPL(snd_devm_alloc_dir_pages); + +/** + * snd_dma_buffer_mmap - perform mmap of the given DMA buffer + * @dmab: buffer allocation information + * @area: VM area information + * + * Return: zero if successful, or a negative error code + */ +int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + const struct snd_malloc_ops *ops; + + if (!dmab) + return -ENOENT; + ops = snd_dma_get_ops(dmab); + if (ops && ops->mmap) + return ops->mmap(dmab, area); + else + return -ENOENT; +} +EXPORT_SYMBOL(snd_dma_buffer_mmap); + +#ifdef CONFIG_HAS_DMA +/** + * snd_dma_buffer_sync - sync DMA buffer between CPU and device + * @dmab: buffer allocation information + * @mode: sync mode + */ +void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + const struct snd_malloc_ops *ops; + + if (!dmab || !dmab->dev.need_sync) + return; + ops = snd_dma_get_ops(dmab); + if (ops && ops->sync) + ops->sync(dmab, mode); +} +EXPORT_SYMBOL_GPL(snd_dma_buffer_sync); +#endif /* CONFIG_HAS_DMA */ + +/** + * snd_sgbuf_get_addr - return the physical address at the corresponding offset + * @dmab: buffer allocation information + * @offset: offset in the ring buffer + * + * Return: the physical address + */ +dma_addr_t snd_sgbuf_get_addr(struct snd_dma_buffer *dmab, size_t offset) +{ + const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); + + if (ops && ops->get_addr) + return ops->get_addr(dmab, offset); + else + return dmab->addr + offset; +} +EXPORT_SYMBOL(snd_sgbuf_get_addr); + +/** + * snd_sgbuf_get_page - return the physical page at the corresponding offset + * @dmab: buffer allocation information + * @offset: offset in the ring buffer + * + * Return: the page pointer + */ +struct page *snd_sgbuf_get_page(struct snd_dma_buffer *dmab, size_t offset) +{ + const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); + + if (ops && ops->get_page) + return ops->get_page(dmab, offset); + else + return virt_to_page(dmab->area + offset); +} +EXPORT_SYMBOL(snd_sgbuf_get_page); + +/** + * snd_sgbuf_get_chunk_size - compute the max chunk size with continuous pages + * on sg-buffer + * @dmab: buffer allocation information + * @ofs: offset in the ring buffer + * @size: the requested size + * + * Return: the chunk size + */ +unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, + unsigned int ofs, unsigned int size) +{ + const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); + + if (ops && ops->get_chunk_size) + return ops->get_chunk_size(dmab, ofs, size); + else + return size; +} +EXPORT_SYMBOL(snd_sgbuf_get_chunk_size); + +/* + * Continuous pages allocator + */ +static void *do_alloc_pages(struct device *dev, size_t size, dma_addr_t *addr, + bool wc) +{ + void *p; + gfp_t gfp = GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN; + + again: + p = alloc_pages_exact(size, gfp); + if (!p) + return NULL; + *addr = page_to_phys(virt_to_page(p)); + if (!dev) + return p; + if ((*addr + size - 1) & ~dev->coherent_dma_mask) { + if (IS_ENABLED(CONFIG_ZONE_DMA32) && !(gfp & GFP_DMA32)) { + gfp |= GFP_DMA32; + goto again; + } + if (IS_ENABLED(CONFIG_ZONE_DMA) && !(gfp & GFP_DMA)) { + gfp = (gfp & ~GFP_DMA32) | GFP_DMA; + goto again; + } + } +#ifdef CONFIG_X86 + if (wc) + set_memory_wc((unsigned long)(p), size >> PAGE_SHIFT); +#endif + return p; +} + +static void do_free_pages(void *p, size_t size, bool wc) +{ +#ifdef CONFIG_X86 + if (wc) + set_memory_wb((unsigned long)(p), size >> PAGE_SHIFT); +#endif + free_pages_exact(p, size); +} + + +static void *snd_dma_continuous_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + return do_alloc_pages(dmab->dev.dev, size, &dmab->addr, false); +} + +static void snd_dma_continuous_free(struct snd_dma_buffer *dmab) +{ + do_free_pages(dmab->area, dmab->bytes, false); +} + +static int snd_dma_continuous_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + return remap_pfn_range(area, area->vm_start, + dmab->addr >> PAGE_SHIFT, + area->vm_end - area->vm_start, + area->vm_page_prot); +} + +static const struct snd_malloc_ops snd_dma_continuous_ops = { + .alloc = snd_dma_continuous_alloc, + .free = snd_dma_continuous_free, + .mmap = snd_dma_continuous_mmap, +}; + +/* + * VMALLOC allocator + */ +static void *snd_dma_vmalloc_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + return vmalloc(size); +} + +static void snd_dma_vmalloc_free(struct snd_dma_buffer *dmab) +{ + vfree(dmab->area); +} + +static int snd_dma_vmalloc_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + return remap_vmalloc_range(area, dmab->area, 0); +} + +#define get_vmalloc_page_addr(dmab, offset) \ + page_to_phys(vmalloc_to_page((dmab)->area + (offset))) + +static dma_addr_t snd_dma_vmalloc_get_addr(struct snd_dma_buffer *dmab, + size_t offset) +{ + return get_vmalloc_page_addr(dmab, offset) + offset % PAGE_SIZE; +} + +static struct page *snd_dma_vmalloc_get_page(struct snd_dma_buffer *dmab, + size_t offset) +{ + return vmalloc_to_page(dmab->area + offset); +} + +static unsigned int +snd_dma_vmalloc_get_chunk_size(struct snd_dma_buffer *dmab, + unsigned int ofs, unsigned int size) +{ + unsigned int start, end; + unsigned long addr; + + start = ALIGN_DOWN(ofs, PAGE_SIZE); + end = ofs + size - 1; /* the last byte address */ + /* check page continuity */ + addr = get_vmalloc_page_addr(dmab, start); + for (;;) { + start += PAGE_SIZE; + if (start > end) + break; + addr += PAGE_SIZE; + if (get_vmalloc_page_addr(dmab, start) != addr) + return start - ofs; + } + /* ok, all on continuous pages */ + return size; +} + +static const struct snd_malloc_ops snd_dma_vmalloc_ops = { + .alloc = snd_dma_vmalloc_alloc, + .free = snd_dma_vmalloc_free, + .mmap = snd_dma_vmalloc_mmap, + .get_addr = snd_dma_vmalloc_get_addr, + .get_page = snd_dma_vmalloc_get_page, + .get_chunk_size = snd_dma_vmalloc_get_chunk_size, +}; + +#ifdef CONFIG_HAS_DMA +/* + * IRAM allocator + */ +#ifdef CONFIG_GENERIC_ALLOCATOR +static void *snd_dma_iram_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + struct device *dev = dmab->dev.dev; + struct gen_pool *pool; + void *p; + + if (dev->of_node) { + pool = of_gen_pool_get(dev->of_node, "iram", 0); + /* Assign the pool into private_data field */ + dmab->private_data = pool; + + p = gen_pool_dma_alloc_align(pool, size, &dmab->addr, PAGE_SIZE); + if (p) + return p; + } + + /* Internal memory might have limited size and no enough space, + * so if we fail to malloc, try to fetch memory traditionally. + */ + dmab->dev.type = SNDRV_DMA_TYPE_DEV; + return __snd_dma_alloc_pages(dmab, size); +} + +static void snd_dma_iram_free(struct snd_dma_buffer *dmab) +{ + struct gen_pool *pool = dmab->private_data; + + if (pool && dmab->area) + gen_pool_free(pool, (unsigned long)dmab->area, dmab->bytes); +} + +static int snd_dma_iram_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); + return remap_pfn_range(area, area->vm_start, + dmab->addr >> PAGE_SHIFT, + area->vm_end - area->vm_start, + area->vm_page_prot); +} + +static const struct snd_malloc_ops snd_dma_iram_ops = { + .alloc = snd_dma_iram_alloc, + .free = snd_dma_iram_free, + .mmap = snd_dma_iram_mmap, +}; +#endif /* CONFIG_GENERIC_ALLOCATOR */ + +/* + * Coherent device pages allocator + */ +static void *snd_dma_dev_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + return dma_alloc_coherent(dmab->dev.dev, size, &dmab->addr, DEFAULT_GFP); +} + +static void snd_dma_dev_free(struct snd_dma_buffer *dmab) +{ + dma_free_coherent(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr); +} + +static int snd_dma_dev_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + return dma_mmap_coherent(dmab->dev.dev, area, + dmab->area, dmab->addr, dmab->bytes); +} + +static const struct snd_malloc_ops snd_dma_dev_ops = { + .alloc = snd_dma_dev_alloc, + .free = snd_dma_dev_free, + .mmap = snd_dma_dev_mmap, +}; + +/* + * Write-combined pages + */ +/* x86-specific allocations */ +#ifdef CONFIG_SND_DMA_SGBUF +static void *snd_dma_wc_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + return do_alloc_pages(dmab->dev.dev, size, &dmab->addr, true); +} + +static void snd_dma_wc_free(struct snd_dma_buffer *dmab) +{ + do_free_pages(dmab->area, dmab->bytes, true); +} + +static int snd_dma_wc_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); + return snd_dma_continuous_mmap(dmab, area); +} +#else +static void *snd_dma_wc_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + return dma_alloc_wc(dmab->dev.dev, size, &dmab->addr, DEFAULT_GFP); +} + +static void snd_dma_wc_free(struct snd_dma_buffer *dmab) +{ + dma_free_wc(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr); +} + +static int snd_dma_wc_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + return dma_mmap_wc(dmab->dev.dev, area, + dmab->area, dmab->addr, dmab->bytes); +} +#endif /* CONFIG_SND_DMA_SGBUF */ + +static const struct snd_malloc_ops snd_dma_wc_ops = { + .alloc = snd_dma_wc_alloc, + .free = snd_dma_wc_free, + .mmap = snd_dma_wc_mmap, +}; + +/* + * Non-contiguous pages allocator + */ +static void *snd_dma_noncontig_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + struct sg_table *sgt; + void *p; + +#ifdef CONFIG_SND_DMA_SGBUF + if (cpu_feature_enabled(X86_FEATURE_XENPV)) + return snd_dma_sg_fallback_alloc(dmab, size); +#endif + sgt = dma_alloc_noncontiguous(dmab->dev.dev, size, dmab->dev.dir, + DEFAULT_GFP, 0); +#ifdef CONFIG_SND_DMA_SGBUF + if (!sgt && !get_dma_ops(dmab->dev.dev)) + return snd_dma_sg_fallback_alloc(dmab, size); +#endif + if (!sgt) + return NULL; + + dmab->dev.need_sync = dma_need_sync(dmab->dev.dev, + sg_dma_address(sgt->sgl)); + p = dma_vmap_noncontiguous(dmab->dev.dev, size, sgt); + if (p) { + dmab->private_data = sgt; + /* store the first page address for convenience */ + dmab->addr = snd_sgbuf_get_addr(dmab, 0); + } else { + dma_free_noncontiguous(dmab->dev.dev, size, sgt, dmab->dev.dir); + } + return p; +} + +static void snd_dma_noncontig_free(struct snd_dma_buffer *dmab) +{ + dma_vunmap_noncontiguous(dmab->dev.dev, dmab->area); + dma_free_noncontiguous(dmab->dev.dev, dmab->bytes, dmab->private_data, + dmab->dev.dir); +} + +static int snd_dma_noncontig_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + return dma_mmap_noncontiguous(dmab->dev.dev, area, + dmab->bytes, dmab->private_data); +} + +static void snd_dma_noncontig_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + if (mode == SNDRV_DMA_SYNC_CPU) { + if (dmab->dev.dir == DMA_TO_DEVICE) + return; + invalidate_kernel_vmap_range(dmab->area, dmab->bytes); + dma_sync_sgtable_for_cpu(dmab->dev.dev, dmab->private_data, + dmab->dev.dir); + } else { + if (dmab->dev.dir == DMA_FROM_DEVICE) + return; + flush_kernel_vmap_range(dmab->area, dmab->bytes); + dma_sync_sgtable_for_device(dmab->dev.dev, dmab->private_data, + dmab->dev.dir); + } +} + +static inline void snd_dma_noncontig_iter_set(struct snd_dma_buffer *dmab, + struct sg_page_iter *piter, + size_t offset) +{ + struct sg_table *sgt = dmab->private_data; + + __sg_page_iter_start(piter, sgt->sgl, sgt->orig_nents, + offset >> PAGE_SHIFT); +} + +static dma_addr_t snd_dma_noncontig_get_addr(struct snd_dma_buffer *dmab, + size_t offset) +{ + struct sg_dma_page_iter iter; + + snd_dma_noncontig_iter_set(dmab, &iter.base, offset); + __sg_page_iter_dma_next(&iter); + return sg_page_iter_dma_address(&iter) + offset % PAGE_SIZE; +} + +static struct page *snd_dma_noncontig_get_page(struct snd_dma_buffer *dmab, + size_t offset) +{ + struct sg_page_iter iter; + + snd_dma_noncontig_iter_set(dmab, &iter, offset); + __sg_page_iter_next(&iter); + return sg_page_iter_page(&iter); +} + +static unsigned int +snd_dma_noncontig_get_chunk_size(struct snd_dma_buffer *dmab, + unsigned int ofs, unsigned int size) +{ + struct sg_dma_page_iter iter; + unsigned int start, end; + unsigned long addr; + + start = ALIGN_DOWN(ofs, PAGE_SIZE); + end = ofs + size - 1; /* the last byte address */ + snd_dma_noncontig_iter_set(dmab, &iter.base, start); + if (!__sg_page_iter_dma_next(&iter)) + return 0; + /* check page continuity */ + addr = sg_page_iter_dma_address(&iter); + for (;;) { + start += PAGE_SIZE; + if (start > end) + break; + addr += PAGE_SIZE; + if (!__sg_page_iter_dma_next(&iter) || + sg_page_iter_dma_address(&iter) != addr) + return start - ofs; + } + /* ok, all on continuous pages */ + return size; +} + +static const struct snd_malloc_ops snd_dma_noncontig_ops = { + .alloc = snd_dma_noncontig_alloc, + .free = snd_dma_noncontig_free, + .mmap = snd_dma_noncontig_mmap, + .sync = snd_dma_noncontig_sync, + .get_addr = snd_dma_noncontig_get_addr, + .get_page = snd_dma_noncontig_get_page, + .get_chunk_size = snd_dma_noncontig_get_chunk_size, +}; + +/* x86-specific SG-buffer with WC pages */ +#ifdef CONFIG_SND_DMA_SGBUF +#define sg_wc_address(it) ((unsigned long)page_address(sg_page_iter_page(it))) + +static void *snd_dma_sg_wc_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + void *p = snd_dma_noncontig_alloc(dmab, size); + struct sg_table *sgt = dmab->private_data; + struct sg_page_iter iter; + + if (!p) + return NULL; + if (dmab->dev.type != SNDRV_DMA_TYPE_DEV_WC_SG) + return p; + for_each_sgtable_page(sgt, &iter, 0) + set_memory_wc(sg_wc_address(&iter), 1); + return p; +} + +static void snd_dma_sg_wc_free(struct snd_dma_buffer *dmab) +{ + struct sg_table *sgt = dmab->private_data; + struct sg_page_iter iter; + + for_each_sgtable_page(sgt, &iter, 0) + set_memory_wb(sg_wc_address(&iter), 1); + snd_dma_noncontig_free(dmab); +} + +static int snd_dma_sg_wc_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); + return dma_mmap_noncontiguous(dmab->dev.dev, area, + dmab->bytes, dmab->private_data); +} + +static const struct snd_malloc_ops snd_dma_sg_wc_ops = { + .alloc = snd_dma_sg_wc_alloc, + .free = snd_dma_sg_wc_free, + .mmap = snd_dma_sg_wc_mmap, + .sync = snd_dma_noncontig_sync, + .get_addr = snd_dma_noncontig_get_addr, + .get_page = snd_dma_noncontig_get_page, + .get_chunk_size = snd_dma_noncontig_get_chunk_size, +}; + +/* Fallback SG-buffer allocations for x86 */ +struct snd_dma_sg_fallback { + bool use_dma_alloc_coherent; + size_t count; + struct page **pages; + /* DMA address array; the first page contains #pages in ~PAGE_MASK */ + dma_addr_t *addrs; +}; + +static void __snd_dma_sg_fallback_free(struct snd_dma_buffer *dmab, + struct snd_dma_sg_fallback *sgbuf) +{ + size_t i, size; + + if (sgbuf->pages && sgbuf->addrs) { + i = 0; + while (i < sgbuf->count) { + if (!sgbuf->pages[i] || !sgbuf->addrs[i]) + break; + size = sgbuf->addrs[i] & ~PAGE_MASK; + if (WARN_ON(!size)) + break; + if (sgbuf->use_dma_alloc_coherent) + dma_free_coherent(dmab->dev.dev, size << PAGE_SHIFT, + page_address(sgbuf->pages[i]), + sgbuf->addrs[i] & PAGE_MASK); + else + do_free_pages(page_address(sgbuf->pages[i]), + size << PAGE_SHIFT, false); + i += size; + } + } + kvfree(sgbuf->pages); + kvfree(sgbuf->addrs); + kfree(sgbuf); +} + +static void *snd_dma_sg_fallback_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + struct snd_dma_sg_fallback *sgbuf; + struct page **pagep, *curp; + size_t chunk, npages; + dma_addr_t *addrp; + dma_addr_t addr; + void *p; + + /* correct the type */ + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_SG) + dmab->dev.type = SNDRV_DMA_TYPE_DEV_SG_FALLBACK; + else if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) + dmab->dev.type = SNDRV_DMA_TYPE_DEV_WC_SG_FALLBACK; + + sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL); + if (!sgbuf) + return NULL; + sgbuf->use_dma_alloc_coherent = cpu_feature_enabled(X86_FEATURE_XENPV); + size = PAGE_ALIGN(size); + sgbuf->count = size >> PAGE_SHIFT; + sgbuf->pages = kvcalloc(sgbuf->count, sizeof(*sgbuf->pages), GFP_KERNEL); + sgbuf->addrs = kvcalloc(sgbuf->count, sizeof(*sgbuf->addrs), GFP_KERNEL); + if (!sgbuf->pages || !sgbuf->addrs) + goto error; + + pagep = sgbuf->pages; + addrp = sgbuf->addrs; + chunk = (PAGE_SIZE - 1) << PAGE_SHIFT; /* to fit in low bits in addrs */ + while (size > 0) { + chunk = min(size, chunk); + if (sgbuf->use_dma_alloc_coherent) + p = dma_alloc_coherent(dmab->dev.dev, chunk, &addr, DEFAULT_GFP); + else + p = do_alloc_pages(dmab->dev.dev, chunk, &addr, false); + if (!p) { + if (chunk <= PAGE_SIZE) + goto error; + chunk >>= 1; + chunk = PAGE_SIZE << get_order(chunk); + continue; + } + + size -= chunk; + /* fill pages */ + npages = chunk >> PAGE_SHIFT; + *addrp = npages; /* store in lower bits */ + curp = virt_to_page(p); + while (npages--) { + *pagep++ = curp++; + *addrp++ |= addr; + addr += PAGE_SIZE; + } + } + + p = vmap(sgbuf->pages, sgbuf->count, VM_MAP, PAGE_KERNEL); + if (!p) + goto error; + + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG_FALLBACK) + set_pages_array_wc(sgbuf->pages, sgbuf->count); + + dmab->private_data = sgbuf; + /* store the first page address for convenience */ + dmab->addr = sgbuf->addrs[0] & PAGE_MASK; + return p; + + error: + __snd_dma_sg_fallback_free(dmab, sgbuf); + return NULL; +} + +static void snd_dma_sg_fallback_free(struct snd_dma_buffer *dmab) +{ + struct snd_dma_sg_fallback *sgbuf = dmab->private_data; + + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG_FALLBACK) + set_pages_array_wb(sgbuf->pages, sgbuf->count); + vunmap(dmab->area); + __snd_dma_sg_fallback_free(dmab, dmab->private_data); +} + +static dma_addr_t snd_dma_sg_fallback_get_addr(struct snd_dma_buffer *dmab, + size_t offset) +{ + struct snd_dma_sg_fallback *sgbuf = dmab->private_data; + size_t index = offset >> PAGE_SHIFT; + + return (sgbuf->addrs[index] & PAGE_MASK) | (offset & ~PAGE_MASK); +} + +static int snd_dma_sg_fallback_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + struct snd_dma_sg_fallback *sgbuf = dmab->private_data; + + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG_FALLBACK) + area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); + return vm_map_pages(area, sgbuf->pages, sgbuf->count); +} + +static const struct snd_malloc_ops snd_dma_sg_fallback_ops = { + .alloc = snd_dma_sg_fallback_alloc, + .free = snd_dma_sg_fallback_free, + .mmap = snd_dma_sg_fallback_mmap, + .get_addr = snd_dma_sg_fallback_get_addr, + /* reuse vmalloc helpers */ + .get_page = snd_dma_vmalloc_get_page, + .get_chunk_size = snd_dma_vmalloc_get_chunk_size, +}; +#endif /* CONFIG_SND_DMA_SGBUF */ + +/* + * Non-coherent pages allocator + */ +static void *snd_dma_noncoherent_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + void *p; + + p = dma_alloc_noncoherent(dmab->dev.dev, size, &dmab->addr, + dmab->dev.dir, DEFAULT_GFP); + if (p) + dmab->dev.need_sync = dma_need_sync(dmab->dev.dev, dmab->addr); + return p; +} + +static void snd_dma_noncoherent_free(struct snd_dma_buffer *dmab) +{ + dma_free_noncoherent(dmab->dev.dev, dmab->bytes, dmab->area, + dmab->addr, dmab->dev.dir); +} + +static int snd_dma_noncoherent_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + area->vm_page_prot = vm_get_page_prot(area->vm_flags); + return dma_mmap_pages(dmab->dev.dev, area, + area->vm_end - area->vm_start, + virt_to_page(dmab->area)); +} + +static void snd_dma_noncoherent_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + if (mode == SNDRV_DMA_SYNC_CPU) { + if (dmab->dev.dir != DMA_TO_DEVICE) + dma_sync_single_for_cpu(dmab->dev.dev, dmab->addr, + dmab->bytes, dmab->dev.dir); + } else { + if (dmab->dev.dir != DMA_FROM_DEVICE) + dma_sync_single_for_device(dmab->dev.dev, dmab->addr, + dmab->bytes, dmab->dev.dir); + } +} + +static const struct snd_malloc_ops snd_dma_noncoherent_ops = { + .alloc = snd_dma_noncoherent_alloc, + .free = snd_dma_noncoherent_free, + .mmap = snd_dma_noncoherent_mmap, + .sync = snd_dma_noncoherent_sync, +}; + +#endif /* CONFIG_HAS_DMA */ + +/* + * Entry points + */ +static const struct snd_malloc_ops *snd_dma_ops[] = { + [SNDRV_DMA_TYPE_CONTINUOUS] = &snd_dma_continuous_ops, + [SNDRV_DMA_TYPE_VMALLOC] = &snd_dma_vmalloc_ops, +#ifdef CONFIG_HAS_DMA + [SNDRV_DMA_TYPE_DEV] = &snd_dma_dev_ops, + [SNDRV_DMA_TYPE_DEV_WC] = &snd_dma_wc_ops, + [SNDRV_DMA_TYPE_NONCONTIG] = &snd_dma_noncontig_ops, + [SNDRV_DMA_TYPE_NONCOHERENT] = &snd_dma_noncoherent_ops, +#ifdef CONFIG_SND_DMA_SGBUF + [SNDRV_DMA_TYPE_DEV_WC_SG] = &snd_dma_sg_wc_ops, +#endif +#ifdef CONFIG_GENERIC_ALLOCATOR + [SNDRV_DMA_TYPE_DEV_IRAM] = &snd_dma_iram_ops, +#endif /* CONFIG_GENERIC_ALLOCATOR */ +#ifdef CONFIG_SND_DMA_SGBUF + [SNDRV_DMA_TYPE_DEV_SG_FALLBACK] = &snd_dma_sg_fallback_ops, + [SNDRV_DMA_TYPE_DEV_WC_SG_FALLBACK] = &snd_dma_sg_fallback_ops, +#endif +#endif /* CONFIG_HAS_DMA */ +}; + +static const struct snd_malloc_ops *snd_dma_get_ops(struct snd_dma_buffer *dmab) +{ + if (WARN_ON_ONCE(!dmab)) + return NULL; + if (WARN_ON_ONCE(dmab->dev.type <= SNDRV_DMA_TYPE_UNKNOWN || + dmab->dev.type >= ARRAY_SIZE(snd_dma_ops))) + return NULL; + return snd_dma_ops[dmab->dev.type]; +} diff --git a/sound/core/memalloc_local.h b/sound/core/memalloc_local.h new file mode 100644 index 0000000000..8b19f3a68a --- /dev/null +++ b/sound/core/memalloc_local.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-only +#ifndef __MEMALLOC_LOCAL_H +#define __MEMALLOC_LOCAL_H + +struct snd_malloc_ops { + void *(*alloc)(struct snd_dma_buffer *dmab, size_t size); + void (*free)(struct snd_dma_buffer *dmab); + dma_addr_t (*get_addr)(struct snd_dma_buffer *dmab, size_t offset); + struct page *(*get_page)(struct snd_dma_buffer *dmab, size_t offset); + unsigned int (*get_chunk_size)(struct snd_dma_buffer *dmab, + unsigned int ofs, unsigned int size); + int (*mmap)(struct snd_dma_buffer *dmab, struct vm_area_struct *area); + void (*sync)(struct snd_dma_buffer *dmab, enum snd_dma_sync_mode mode); +}; + +#endif /* __MEMALLOC_LOCAL_H */ diff --git a/sound/core/memory.c b/sound/core/memory.c new file mode 100644 index 0000000000..2d2d0094c8 --- /dev/null +++ b/sound/core/memory.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * + * Misc memory accessors + */ + +#include <linux/export.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <sound/core.h> +#include <sound/pcm.h> + +/** + * copy_to_user_fromio - copy data from mmio-space to user-space + * @dst: the destination pointer on user-space + * @src: the source pointer on mmio + * @count: the data size to copy in bytes + * + * Copies the data from mmio-space to user-space. + * + * Return: Zero if successful, or non-zero on failure. + */ +int copy_to_user_fromio(void __user *dst, const volatile void __iomem *src, size_t count) +{ + struct iov_iter iter; + + if (import_ubuf(ITER_DEST, dst, count, &iter)) + return -EFAULT; + return copy_to_iter_fromio(&iter, (const void __iomem *)src, count); +} +EXPORT_SYMBOL(copy_to_user_fromio); + +/** + * copy_to_iter_fromio - copy data from mmio-space to iov_iter + * @dst: the destination iov_iter + * @src: the source pointer on mmio + * @count: the data size to copy in bytes + * + * Copies the data from mmio-space to iov_iter. + * + * Return: Zero if successful, or non-zero on failure. + */ +int copy_to_iter_fromio(struct iov_iter *dst, const void __iomem *src, + size_t count) +{ +#if defined(__i386__) || defined(CONFIG_SPARC32) + return copy_to_iter((const void __force *)src, count, dst) == count ? 0 : -EFAULT; +#else + char buf[256]; + while (count) { + size_t c = count; + if (c > sizeof(buf)) + c = sizeof(buf); + memcpy_fromio(buf, (void __iomem *)src, c); + if (copy_to_iter(buf, c, dst) != c) + return -EFAULT; + count -= c; + src += c; + } + return 0; +#endif +} +EXPORT_SYMBOL(copy_to_iter_fromio); + +/** + * copy_from_user_toio - copy data from user-space to mmio-space + * @dst: the destination pointer on mmio-space + * @src: the source pointer on user-space + * @count: the data size to copy in bytes + * + * Copies the data from user-space to mmio-space. + * + * Return: Zero if successful, or non-zero on failure. + */ +int copy_from_user_toio(volatile void __iomem *dst, const void __user *src, size_t count) +{ + struct iov_iter iter; + + if (import_ubuf(ITER_SOURCE, (void __user *)src, count, &iter)) + return -EFAULT; + return copy_from_iter_toio((void __iomem *)dst, &iter, count); +} +EXPORT_SYMBOL(copy_from_user_toio); + +/** + * copy_from_iter_toio - copy data from iov_iter to mmio-space + * @dst: the destination pointer on mmio-space + * @src: the source iov_iter + * @count: the data size to copy in bytes + * + * Copies the data from iov_iter to mmio-space. + * + * Return: Zero if successful, or non-zero on failure. + */ +int copy_from_iter_toio(void __iomem *dst, struct iov_iter *src, size_t count) +{ +#if defined(__i386__) || defined(CONFIG_SPARC32) + return copy_from_iter((void __force *)dst, count, src) == count ? 0 : -EFAULT; +#else + char buf[256]; + while (count) { + size_t c = count; + if (c > sizeof(buf)) + c = sizeof(buf); + if (copy_from_iter(buf, c, src) != c) + return -EFAULT; + memcpy_toio(dst, buf, c); + count -= c; + dst += c; + } + return 0; +#endif +} +EXPORT_SYMBOL(copy_from_iter_toio); diff --git a/sound/core/misc.c b/sound/core/misc.c new file mode 100644 index 0000000000..d32a19976a --- /dev/null +++ b/sound/core/misc.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Misc and compatibility things + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/moduleparam.h> +#include <linux/time.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/fs.h> +#include <sound/core.h> + +#ifdef CONFIG_SND_DEBUG + +#ifdef CONFIG_SND_DEBUG_VERBOSE +#define DEFAULT_DEBUG_LEVEL 2 +#else +#define DEFAULT_DEBUG_LEVEL 1 +#endif + +static int debug = DEFAULT_DEBUG_LEVEL; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0 = disable)"); + +#endif /* CONFIG_SND_DEBUG */ + +void release_and_free_resource(struct resource *res) +{ + if (res) { + release_resource(res); + kfree(res); + } +} +EXPORT_SYMBOL(release_and_free_resource); + +#ifdef CONFIG_SND_VERBOSE_PRINTK +/* strip the leading path if the given path is absolute */ +static const char *sanity_file_name(const char *path) +{ + if (*path == '/') + return strrchr(path, '/') + 1; + else + return path; +} +#endif + +#if defined(CONFIG_SND_DEBUG) || defined(CONFIG_SND_VERBOSE_PRINTK) +void __snd_printk(unsigned int level, const char *path, int line, + const char *format, ...) +{ + va_list args; +#ifdef CONFIG_SND_VERBOSE_PRINTK + int kern_level; + struct va_format vaf; + char verbose_fmt[] = KERN_DEFAULT "ALSA %s:%d %pV"; + bool level_found = false; +#endif + +#ifdef CONFIG_SND_DEBUG + if (debug < level) + return; +#endif + + va_start(args, format); +#ifdef CONFIG_SND_VERBOSE_PRINTK + vaf.fmt = format; + vaf.va = &args; + + while ((kern_level = printk_get_level(vaf.fmt)) != 0) { + const char *end_of_header = printk_skip_level(vaf.fmt); + + /* Ignore KERN_CONT. We print filename:line for each piece. */ + if (kern_level >= '0' && kern_level <= '7') { + memcpy(verbose_fmt, vaf.fmt, end_of_header - vaf.fmt); + level_found = true; + } + + vaf.fmt = end_of_header; + } + + if (!level_found && level) + memcpy(verbose_fmt, KERN_DEBUG, sizeof(KERN_DEBUG) - 1); + + printk(verbose_fmt, sanity_file_name(path), line, &vaf); +#else + vprintk(format, args); +#endif + va_end(args); +} +EXPORT_SYMBOL_GPL(__snd_printk); +#endif + +#ifdef CONFIG_PCI +#include <linux/pci.h> +/** + * snd_pci_quirk_lookup_id - look up a PCI SSID quirk list + * @vendor: PCI SSV id + * @device: PCI SSD id + * @list: quirk list, terminated by a null entry + * + * Look through the given quirk list and finds a matching entry + * with the same PCI SSID. When subdevice is 0, all subdevice + * values may match. + * + * Returns the matched entry pointer, or NULL if nothing matched. + */ +const struct snd_pci_quirk * +snd_pci_quirk_lookup_id(u16 vendor, u16 device, + const struct snd_pci_quirk *list) +{ + const struct snd_pci_quirk *q; + + for (q = list; q->subvendor || q->subdevice; q++) { + if (q->subvendor != vendor) + continue; + if (!q->subdevice || + (device & q->subdevice_mask) == q->subdevice) + return q; + } + return NULL; +} +EXPORT_SYMBOL(snd_pci_quirk_lookup_id); + +/** + * snd_pci_quirk_lookup - look up a PCI SSID quirk list + * @pci: pci_dev handle + * @list: quirk list, terminated by a null entry + * + * Look through the given quirk list and finds a matching entry + * with the same PCI SSID. When subdevice is 0, all subdevice + * values may match. + * + * Returns the matched entry pointer, or NULL if nothing matched. + */ +const struct snd_pci_quirk * +snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list) +{ + if (!pci) + return NULL; + return snd_pci_quirk_lookup_id(pci->subsystem_vendor, + pci->subsystem_device, + list); +} +EXPORT_SYMBOL(snd_pci_quirk_lookup); +#endif + +/* + * Deferred async signal helpers + * + * Below are a few helper functions to wrap the async signal handling + * in the deferred work. The main purpose is to avoid the messy deadlock + * around tasklist_lock and co at the kill_fasync() invocation. + * fasync_helper() and kill_fasync() are replaced with snd_fasync_helper() + * and snd_kill_fasync(), respectively. In addition, snd_fasync_free() has + * to be called at releasing the relevant file object. + */ +struct snd_fasync { + struct fasync_struct *fasync; + int signal; + int poll; + int on; + struct list_head list; +}; + +static DEFINE_SPINLOCK(snd_fasync_lock); +static LIST_HEAD(snd_fasync_list); + +static void snd_fasync_work_fn(struct work_struct *work) +{ + struct snd_fasync *fasync; + + spin_lock_irq(&snd_fasync_lock); + while (!list_empty(&snd_fasync_list)) { + fasync = list_first_entry(&snd_fasync_list, struct snd_fasync, list); + list_del_init(&fasync->list); + spin_unlock_irq(&snd_fasync_lock); + if (fasync->on) + kill_fasync(&fasync->fasync, fasync->signal, fasync->poll); + spin_lock_irq(&snd_fasync_lock); + } + spin_unlock_irq(&snd_fasync_lock); +} + +static DECLARE_WORK(snd_fasync_work, snd_fasync_work_fn); + +int snd_fasync_helper(int fd, struct file *file, int on, + struct snd_fasync **fasyncp) +{ + struct snd_fasync *fasync = NULL; + + if (on) { + fasync = kzalloc(sizeof(*fasync), GFP_KERNEL); + if (!fasync) + return -ENOMEM; + INIT_LIST_HEAD(&fasync->list); + } + + spin_lock_irq(&snd_fasync_lock); + if (*fasyncp) { + kfree(fasync); + fasync = *fasyncp; + } else { + if (!fasync) { + spin_unlock_irq(&snd_fasync_lock); + return 0; + } + *fasyncp = fasync; + } + fasync->on = on; + spin_unlock_irq(&snd_fasync_lock); + return fasync_helper(fd, file, on, &fasync->fasync); +} +EXPORT_SYMBOL_GPL(snd_fasync_helper); + +void snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll) +{ + unsigned long flags; + + if (!fasync || !fasync->on) + return; + spin_lock_irqsave(&snd_fasync_lock, flags); + fasync->signal = signal; + fasync->poll = poll; + list_move(&fasync->list, &snd_fasync_list); + schedule_work(&snd_fasync_work); + spin_unlock_irqrestore(&snd_fasync_lock, flags); +} +EXPORT_SYMBOL_GPL(snd_kill_fasync); + +void snd_fasync_free(struct snd_fasync *fasync) +{ + if (!fasync) + return; + fasync->on = 0; + flush_work(&snd_fasync_work); + kfree(fasync); +} +EXPORT_SYMBOL_GPL(snd_fasync_free); diff --git a/sound/core/oss/Makefile b/sound/core/oss/Makefile new file mode 100644 index 0000000000..ae25edcc3b --- /dev/null +++ b/sound/core/oss/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> +# + +snd-mixer-oss-objs := mixer_oss.o + +snd-pcm-oss-y := pcm_oss.o +snd-pcm-oss-$(CONFIG_SND_PCM_OSS_PLUGINS) += pcm_plugin.o \ + io.o copy.o linear.o mulaw.o route.o rate.o + +obj-$(CONFIG_SND_MIXER_OSS) += snd-mixer-oss.o +obj-$(CONFIG_SND_PCM_OSS) += snd-pcm-oss.o diff --git a/sound/core/oss/copy.c b/sound/core/oss/copy.c new file mode 100644 index 0000000000..05b58d4fc2 --- /dev/null +++ b/sound/core/oss/copy.c @@ -0,0 +1,92 @@ +/* + * Linear conversion Plug-In + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +static snd_pcm_sframes_t copy_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + unsigned int channel; + unsigned int nchannels; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; + nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; channel++) { + if (snd_BUG_ON(src_channels->area.first % 8 || + src_channels->area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels->area.first % 8 || + dst_channels->area.step % 8)) + return -ENXIO; + if (!src_channels->enabled) { + if (dst_channels->wanted) + snd_pcm_area_silence(&dst_channels->area, 0, frames, plugin->dst_format.format); + dst_channels->enabled = 0; + continue; + } + dst_channels->enabled = 1; + snd_pcm_area_copy(&src_channels->area, 0, &dst_channels->area, 0, frames, plugin->src_format.format); + src_channels++; + dst_channels++; + } + return frames; +} + +int snd_pcm_plugin_build_copy(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct snd_pcm_plugin *plugin; + int width; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->format != dst_format->format)) + return -ENXIO; + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + + width = snd_pcm_format_physical_width(src_format->format); + if (snd_BUG_ON(width <= 0)) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "copy", src_format, dst_format, + 0, &plugin); + if (err < 0) + return err; + plugin->transfer = copy_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/io.c b/sound/core/oss/io.c new file mode 100644 index 0000000000..d870b2d931 --- /dev/null +++ b/sound/core/oss/io.c @@ -0,0 +1,141 @@ +/* + * PCM I/O Plug-In Interface + * Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "pcm_plugin.h" + +#define pcm_write(plug,buf,count) snd_pcm_oss_write3(plug,buf,count,1) +#define pcm_writev(plug,vec,count) snd_pcm_oss_writev3(plug,vec,count) +#define pcm_read(plug,buf,count) snd_pcm_oss_read3(plug,buf,count,1) +#define pcm_readv(plug,vec,count) snd_pcm_oss_readv3(plug,vec,count) + +/* + * Basic io plugin + */ + +static snd_pcm_sframes_t io_playback_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (snd_BUG_ON(!src_channels)) + return -ENXIO; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + return pcm_write(plugin->plug, src_channels->area.addr, frames); + } else { + int channel, channels = plugin->dst_format.channels; + void **bufs = (void**)plugin->extra_data; + if (snd_BUG_ON(!bufs)) + return -ENXIO; + for (channel = 0; channel < channels; channel++) { + if (src_channels[channel].enabled) + bufs[channel] = src_channels[channel].area.addr; + else + bufs[channel] = NULL; + } + return pcm_writev(plugin->plug, bufs, frames); + } +} + +static snd_pcm_sframes_t io_capture_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (snd_BUG_ON(!dst_channels)) + return -ENXIO; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + return pcm_read(plugin->plug, dst_channels->area.addr, frames); + } else { + int channel, channels = plugin->dst_format.channels; + void **bufs = (void**)plugin->extra_data; + if (snd_BUG_ON(!bufs)) + return -ENXIO; + for (channel = 0; channel < channels; channel++) { + if (dst_channels[channel].enabled) + bufs[channel] = dst_channels[channel].area.addr; + else + bufs[channel] = NULL; + } + return pcm_readv(plugin->plug, bufs, frames); + } + return 0; +} + +static snd_pcm_sframes_t io_src_channels(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels) +{ + int err; + unsigned int channel; + struct snd_pcm_plugin_channel *v; + err = snd_pcm_plugin_client_channels(plugin, frames, &v); + if (err < 0) + return err; + *channels = v; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + for (channel = 0; channel < plugin->src_format.channels; ++channel, ++v) + v->wanted = 1; + } + return frames; +} + +int snd_pcm_plugin_build_io(struct snd_pcm_substream *plug, + struct snd_pcm_hw_params *params, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct snd_pcm_plugin_format format; + struct snd_pcm_plugin *plugin; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + if (snd_BUG_ON(!plug || !params)) + return -ENXIO; + format.format = params_format(params); + format.rate = params_rate(params); + format.channels = params_channels(params); + err = snd_pcm_plugin_build(plug, "I/O io", + &format, &format, + sizeof(void *) * format.channels, + &plugin); + if (err < 0) + return err; + plugin->access = params_access(params); + if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) { + plugin->transfer = io_playback_transfer; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) + plugin->client_channels = io_src_channels; + } else { + plugin->transfer = io_capture_transfer; + } + + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/linear.c b/sound/core/oss/linear.c new file mode 100644 index 0000000000..797d838a2f --- /dev/null +++ b/sound/core/oss/linear.c @@ -0,0 +1,180 @@ +/* + * Linear conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz>, + * Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +/* + * Basic linear conversion plugin + */ + +struct linear_priv { + int cvt_endian; /* need endian conversion? */ + unsigned int src_ofs; /* byte offset in source format */ + unsigned int dst_ofs; /* byte soffset in destination format */ + unsigned int copy_ofs; /* byte offset in temporary u32 data */ + unsigned int dst_bytes; /* byte size of destination format */ + unsigned int copy_bytes; /* bytes to copy per conversion */ + unsigned int flip; /* MSB flip for signeness, done after endian conv */ +}; + +static inline void do_convert(struct linear_priv *data, + unsigned char *dst, unsigned char *src) +{ + unsigned int tmp = 0; + unsigned char *p = (unsigned char *)&tmp; + + memcpy(p + data->copy_ofs, src + data->src_ofs, data->copy_bytes); + if (data->cvt_endian) + tmp = swab32(tmp); + tmp ^= data->flip; + memcpy(dst, p + data->dst_ofs, data->dst_bytes); +} + +static void convert(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct linear_priv *data = (struct linear_priv *)plugin->extra_data; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + do_convert(data, dst, src); + src += src_step; + dst += dst_step; + } + } +} + +static snd_pcm_sframes_t linear_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + if (snd_BUG_ON(src_channels[channel].area.first % 8 || + src_channels[channel].area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels[channel].area.first % 8 || + dst_channels[channel].area.step % 8)) + return -ENXIO; + } + } +#endif + if (frames > dst_channels[0].frames) + frames = dst_channels[0].frames; + convert(plugin, src_channels, dst_channels, frames); + return frames; +} + +static void init_data(struct linear_priv *data, + snd_pcm_format_t src_format, snd_pcm_format_t dst_format) +{ + int src_le, dst_le, src_bytes, dst_bytes; + + src_bytes = snd_pcm_format_width(src_format) / 8; + dst_bytes = snd_pcm_format_width(dst_format) / 8; + src_le = snd_pcm_format_little_endian(src_format) > 0; + dst_le = snd_pcm_format_little_endian(dst_format) > 0; + + data->dst_bytes = dst_bytes; + data->cvt_endian = src_le != dst_le; + data->copy_bytes = src_bytes < dst_bytes ? src_bytes : dst_bytes; + if (src_le) { + data->copy_ofs = 4 - data->copy_bytes; + data->src_ofs = src_bytes - data->copy_bytes; + } else + data->src_ofs = snd_pcm_format_physical_width(src_format) / 8 - + src_bytes; + if (dst_le) + data->dst_ofs = 4 - data->dst_bytes; + else + data->dst_ofs = snd_pcm_format_physical_width(dst_format) / 8 - + dst_bytes; + if (snd_pcm_format_signed(src_format) != + snd_pcm_format_signed(dst_format)) { + if (dst_le) + data->flip = (__force u32)cpu_to_le32(0x80000000); + else + data->flip = (__force u32)cpu_to_be32(0x80000000); + } +} + +int snd_pcm_plugin_build_linear(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct linear_priv *data; + struct snd_pcm_plugin *plugin; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + if (snd_BUG_ON(!snd_pcm_format_linear(src_format->format) || + !snd_pcm_format_linear(dst_format->format))) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "linear format conversion", + src_format, dst_format, + sizeof(struct linear_priv), &plugin); + if (err < 0) + return err; + data = (struct linear_priv *)plugin->extra_data; + init_data(data, src_format->format, dst_format->format); + plugin->transfer = linear_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c new file mode 100644 index 0000000000..dae2da3808 --- /dev/null +++ b/sound/core/oss/mixer_oss.c @@ -0,0 +1,1460 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS emulation layer for the mixer interface + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/compat.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/mixer_oss.h> +#include <linux/soundcard.h> + +#define OSS_ALSAEMULVER _SIOR ('M', 249, int) + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Mixer OSS emulation for ALSA."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MIXER); + +static int snd_mixer_oss_open(struct inode *inode, struct file *file) +{ + struct snd_card *card; + struct snd_mixer_oss_file *fmixer; + int err; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + card = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIXER); + if (card == NULL) + return -ENODEV; + if (card->mixer_oss == NULL) { + snd_card_unref(card); + return -ENODEV; + } + err = snd_card_file_add(card, file); + if (err < 0) { + snd_card_unref(card); + return err; + } + fmixer = kzalloc(sizeof(*fmixer), GFP_KERNEL); + if (fmixer == NULL) { + snd_card_file_remove(card, file); + snd_card_unref(card); + return -ENOMEM; + } + fmixer->card = card; + fmixer->mixer = card->mixer_oss; + file->private_data = fmixer; + if (!try_module_get(card->module)) { + kfree(fmixer); + snd_card_file_remove(card, file); + snd_card_unref(card); + return -EFAULT; + } + snd_card_unref(card); + return 0; +} + +static int snd_mixer_oss_release(struct inode *inode, struct file *file) +{ + struct snd_mixer_oss_file *fmixer; + + if (file->private_data) { + fmixer = file->private_data; + module_put(fmixer->card->module); + snd_card_file_remove(fmixer->card, file); + kfree(fmixer); + } + return 0; +} + +static int snd_mixer_oss_info(struct snd_mixer_oss_file *fmixer, + mixer_info __user *_info) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + struct mixer_info info; + + memset(&info, 0, sizeof(info)); + strscpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); + strscpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); + info.modify_counter = card->mixer_oss_change_count; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_mixer_oss_info_obsolete(struct snd_mixer_oss_file *fmixer, + _old_mixer_info __user *_info) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + _old_mixer_info info; + + memset(&info, 0, sizeof(info)); + strscpy(info.id, mixer && mixer->id[0] ? mixer->id : card->driver, sizeof(info.id)); + strscpy(info.name, mixer && mixer->name[0] ? mixer->name : card->mixername, sizeof(info.name)); + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_mixer_oss_caps(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + if (mixer->get_recsrc && mixer->put_recsrc) + result |= SOUND_CAP_EXCL_INPUT; + return result; +} + +static int snd_mixer_oss_devmask(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, chn; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_volume || pslot->put_recsrc) + result |= 1 << chn; + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_stereodevs(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, chn; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_volume && pslot->stereo) + result |= 1 << chn; + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_recmask(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ + result = mixer->mask_recsrc; + } else { + struct snd_mixer_oss_slot *pslot; + int chn; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_recsrc) + result |= 1 << chn; + } + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_get_recsrc(struct snd_mixer_oss_file *fmixer) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + int result = 0; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ + unsigned int index; + result = mixer->get_recsrc(fmixer, &index); + if (result < 0) + goto unlock; + result = 1 << index; + } else { + struct snd_mixer_oss_slot *pslot; + int chn; + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->get_recsrc) { + int active = 0; + pslot->get_recsrc(fmixer, pslot, &active); + if (active) + result |= 1 << chn; + } + } + } + mixer->oss_recsrc = result; + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsrc) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int chn, active; + unsigned int index; + int result = 0; + + if (mixer == NULL) + return -EIO; + mutex_lock(&mixer->reg_mutex); + if (mixer->get_recsrc && mixer->put_recsrc) { /* exclusive input */ + if (recsrc & ~mixer->oss_recsrc) + recsrc &= ~mixer->oss_recsrc; + mixer->put_recsrc(fmixer, ffz(~recsrc)); + mixer->get_recsrc(fmixer, &index); + result = 1 << index; + } + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->put_recsrc) { + active = (recsrc & (1 << chn)) ? 1 : 0; + pslot->put_recsrc(fmixer, pslot, active); + } + } + if (! result) { + for (chn = 0; chn < 31; chn++) { + pslot = &mixer->slots[chn]; + if (pslot->get_recsrc) { + active = 0; + pslot->get_recsrc(fmixer, pslot, &active); + if (active) + result |= 1 << chn; + } + } + } + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_get_volume(struct snd_mixer_oss_file *fmixer, int slot) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, left, right; + + if (mixer == NULL || slot > 30) + return -EIO; + mutex_lock(&mixer->reg_mutex); + pslot = &mixer->slots[slot]; + left = pslot->volume[0]; + right = pslot->volume[1]; + if (pslot->get_volume) + result = pslot->get_volume(fmixer, pslot, &left, &right); + if (!pslot->stereo) + right = left; + if (snd_BUG_ON(left < 0 || left > 100)) { + result = -EIO; + goto unlock; + } + if (snd_BUG_ON(right < 0 || right > 100)) { + result = -EIO; + goto unlock; + } + if (result >= 0) { + pslot->volume[0] = left; + pslot->volume[1] = right; + result = (left & 0xff) | ((right & 0xff) << 8); + } + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_set_volume(struct snd_mixer_oss_file *fmixer, + int slot, int volume) +{ + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_mixer_oss_slot *pslot; + int result = 0, left = volume & 0xff, right = (volume >> 8) & 0xff; + + if (mixer == NULL || slot > 30) + return -EIO; + mutex_lock(&mixer->reg_mutex); + pslot = &mixer->slots[slot]; + if (left > 100) + left = 100; + if (right > 100) + right = 100; + if (!pslot->stereo) + right = left; + if (pslot->put_volume) + result = pslot->put_volume(fmixer, pslot, left, right); + if (result < 0) + goto unlock; + pslot->volume[0] = left; + pslot->volume[1] = right; + result = (left & 0xff) | ((right & 0xff) << 8); + unlock: + mutex_unlock(&mixer->reg_mutex); + return result; +} + +static int snd_mixer_oss_ioctl1(struct snd_mixer_oss_file *fmixer, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int tmp; + + if (snd_BUG_ON(!fmixer)) + return -ENXIO; + if (((cmd >> 8) & 0xff) == 'M') { + switch (cmd) { + case SOUND_MIXER_INFO: + return snd_mixer_oss_info(fmixer, argp); + case SOUND_OLD_MIXER_INFO: + return snd_mixer_oss_info_obsolete(fmixer, argp); + case SOUND_MIXER_WRITE_RECSRC: + if (get_user(tmp, p)) + return -EFAULT; + tmp = snd_mixer_oss_set_recsrc(fmixer, tmp); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case OSS_GETVERSION: + return put_user(SNDRV_OSS_VERSION, p); + case OSS_ALSAEMULVER: + return put_user(1, p); + case SOUND_MIXER_READ_DEVMASK: + tmp = snd_mixer_oss_devmask(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_STEREODEVS: + tmp = snd_mixer_oss_stereodevs(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_RECMASK: + tmp = snd_mixer_oss_recmask(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_CAPS: + tmp = snd_mixer_oss_caps(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + case SOUND_MIXER_READ_RECSRC: + tmp = snd_mixer_oss_get_recsrc(fmixer); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } + } + if (cmd & SIOC_IN) { + if (get_user(tmp, p)) + return -EFAULT; + tmp = snd_mixer_oss_set_volume(fmixer, cmd & 0xff, tmp); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } else if (cmd & SIOC_OUT) { + tmp = snd_mixer_oss_get_volume(fmixer, cmd & 0xff); + if (tmp < 0) + return tmp; + return put_user(tmp, p); + } + return -ENXIO; +} + +static long snd_mixer_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return snd_mixer_oss_ioctl1(file->private_data, cmd, arg); +} + +int snd_mixer_oss_ioctl_card(struct snd_card *card, unsigned int cmd, unsigned long arg) +{ + struct snd_mixer_oss_file fmixer; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (card->mixer_oss == NULL) + return -ENXIO; + memset(&fmixer, 0, sizeof(fmixer)); + fmixer.card = card; + fmixer.mixer = card->mixer_oss; + return snd_mixer_oss_ioctl1(&fmixer, cmd, arg); +} +EXPORT_SYMBOL(snd_mixer_oss_ioctl_card); + +#ifdef CONFIG_COMPAT +/* all compatible */ +static long snd_mixer_oss_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return snd_mixer_oss_ioctl1(file->private_data, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define snd_mixer_oss_ioctl_compat NULL +#endif + +/* + * REGISTRATION PART + */ + +static const struct file_operations snd_mixer_oss_f_ops = +{ + .owner = THIS_MODULE, + .open = snd_mixer_oss_open, + .release = snd_mixer_oss_release, + .llseek = no_llseek, + .unlocked_ioctl = snd_mixer_oss_ioctl, + .compat_ioctl = snd_mixer_oss_ioctl_compat, +}; + +/* + * utilities + */ + +static long snd_mixer_oss_conv(long val, long omin, long omax, long nmin, long nmax) +{ + long orange = omax - omin, nrange = nmax - nmin; + + if (orange == 0) + return 0; + return DIV_ROUND_CLOSEST(nrange * (val - omin), orange) + nmin; +} + +/* convert from alsa native to oss values (0-100) */ +static long snd_mixer_oss_conv1(long val, long min, long max, int *old) +{ + if (val == snd_mixer_oss_conv(*old, 0, 100, min, max)) + return *old; + return snd_mixer_oss_conv(val, min, max, 0, 100); +} + +/* convert from oss to alsa native values */ +static long snd_mixer_oss_conv2(long val, long min, long max) +{ + return snd_mixer_oss_conv(val, 0, 100, min, max); +} + +#if 0 +static void snd_mixer_oss_recsrce_set(struct snd_card *card, int slot) +{ + struct snd_mixer_oss *mixer = card->mixer_oss; + if (mixer) + mixer->mask_recsrc |= 1 << slot; +} + +static int snd_mixer_oss_recsrce_get(struct snd_card *card, int slot) +{ + struct snd_mixer_oss *mixer = card->mixer_oss; + if (mixer && (mixer->mask_recsrc & (1 << slot))) + return 1; + return 0; +} +#endif + +#define SNDRV_MIXER_OSS_SIGNATURE 0x65999250 + +#define SNDRV_MIXER_OSS_ITEM_GLOBAL 0 +#define SNDRV_MIXER_OSS_ITEM_GSWITCH 1 +#define SNDRV_MIXER_OSS_ITEM_GROUTE 2 +#define SNDRV_MIXER_OSS_ITEM_GVOLUME 3 +#define SNDRV_MIXER_OSS_ITEM_PSWITCH 4 +#define SNDRV_MIXER_OSS_ITEM_PROUTE 5 +#define SNDRV_MIXER_OSS_ITEM_PVOLUME 6 +#define SNDRV_MIXER_OSS_ITEM_CSWITCH 7 +#define SNDRV_MIXER_OSS_ITEM_CROUTE 8 +#define SNDRV_MIXER_OSS_ITEM_CVOLUME 9 +#define SNDRV_MIXER_OSS_ITEM_CAPTURE 10 + +#define SNDRV_MIXER_OSS_ITEM_COUNT 11 + +#define SNDRV_MIXER_OSS_PRESENT_GLOBAL (1<<0) +#define SNDRV_MIXER_OSS_PRESENT_GSWITCH (1<<1) +#define SNDRV_MIXER_OSS_PRESENT_GROUTE (1<<2) +#define SNDRV_MIXER_OSS_PRESENT_GVOLUME (1<<3) +#define SNDRV_MIXER_OSS_PRESENT_PSWITCH (1<<4) +#define SNDRV_MIXER_OSS_PRESENT_PROUTE (1<<5) +#define SNDRV_MIXER_OSS_PRESENT_PVOLUME (1<<6) +#define SNDRV_MIXER_OSS_PRESENT_CSWITCH (1<<7) +#define SNDRV_MIXER_OSS_PRESENT_CROUTE (1<<8) +#define SNDRV_MIXER_OSS_PRESENT_CVOLUME (1<<9) +#define SNDRV_MIXER_OSS_PRESENT_CAPTURE (1<<10) + +struct slot { + unsigned int signature; + unsigned int present; + unsigned int channels; + unsigned int numid[SNDRV_MIXER_OSS_ITEM_COUNT]; + unsigned int capture_item; + const struct snd_mixer_oss_assign_table *assigned; + unsigned int allocated: 1; +}; + +#define ID_UNKNOWN ((unsigned int)-1) + +static struct snd_kcontrol *snd_mixer_oss_test_id(struct snd_mixer_oss *mixer, const char *name, int index) +{ + struct snd_card *card = mixer->card; + struct snd_ctl_elem_id id; + + memset(&id, 0, sizeof(id)); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(id.name, name, sizeof(id.name)); + id.index = index; + return snd_ctl_find_id_locked(card, &id); +} + +static void snd_mixer_oss_get_volume1_vol(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int *left, int *right) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_numid_locked(card, numid); + if (!kctl) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (kctl->get(kctl, uctl)) + goto __unalloc; + if (uinfo->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN && + uinfo->value.integer.min == 0 && uinfo->value.integer.max == 1) + goto __unalloc; + *left = snd_mixer_oss_conv1(uctl->value.integer.value[0], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[0]); + if (uinfo->count > 1) + *right = snd_mixer_oss_conv1(uctl->value.integer.value[1], uinfo->value.integer.min, uinfo->value.integer.max, &pslot->volume[1]); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static void snd_mixer_oss_get_volume1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int *left, int *right, + int route) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_numid_locked(card, numid); + if (!kctl) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (kctl->get(kctl, uctl)) + goto __unalloc; + if (!uctl->value.integer.value[0]) { + *left = 0; + if (uinfo->count == 1) + *right = 0; + } + if (uinfo->count > 1 && !uctl->value.integer.value[route ? 3 : 1]) + *right = 0; + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static int snd_mixer_oss_get_volume1(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int *left, int *right) +{ + struct slot *slot = pslot->private_data; + + *left = *right = 100; + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { + snd_mixer_oss_get_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); + } + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } + return 0; +} + +static void snd_mixer_oss_put_volume1_vol(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int left, int right) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + int res; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_numid_locked(card, numid); + if (!kctl) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (uinfo->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN && + uinfo->value.integer.min == 0 && uinfo->value.integer.max == 1) + goto __unalloc; + uctl->value.integer.value[0] = snd_mixer_oss_conv2(left, uinfo->value.integer.min, uinfo->value.integer.max); + if (uinfo->count > 1) + uctl->value.integer.value[1] = snd_mixer_oss_conv2(right, uinfo->value.integer.min, uinfo->value.integer.max); + res = kctl->put(kctl, uctl); + if (res < 0) + goto __unalloc; + if (res > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static void snd_mixer_oss_put_volume1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + unsigned int numid, + int left, int right, + int route) +{ + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + struct snd_kcontrol *kctl; + struct snd_card *card = fmixer->card; + int res; + + if (numid == ID_UNKNOWN) + return; + down_read(&card->controls_rwsem); + kctl = snd_ctl_find_numid_locked(card, numid); + if (!kctl) { + up_read(&card->controls_rwsem); + return; + } + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) + goto __unalloc; + if (kctl->info(kctl, uinfo)) + goto __unalloc; + if (uinfo->count > 1) { + uctl->value.integer.value[0] = left > 0 ? 1 : 0; + uctl->value.integer.value[route ? 3 : 1] = right > 0 ? 1 : 0; + if (route) { + uctl->value.integer.value[1] = + uctl->value.integer.value[2] = 0; + } + } else { + uctl->value.integer.value[0] = (left > 0 || right > 0) ? 1 : 0; + } + res = kctl->put(kctl, uctl); + if (res < 0) + goto __unalloc; + if (res > 0) + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + __unalloc: + up_read(&card->controls_rwsem); + kfree(uctl); + kfree(uinfo); +} + +static int snd_mixer_oss_put_volume1(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int left, int right) +{ + struct slot *slot = pslot->private_data; + + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PVOLUME], left, right); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME) + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, + slot->numid[SNDRV_MIXER_OSS_ITEM_CVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GVOLUME) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GVOLUME], left, right); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GLOBAL) { + snd_mixer_oss_put_volume1_vol(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GLOBAL], left, right); + } + if (left || right) { + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_CROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], left, right, 1); + if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } else { + if (slot->present & SNDRV_MIXER_OSS_PRESENT_PSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GSWITCH) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GSWITCH], left, right, 0); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_PROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_PROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_CROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], left, right, 1); + } else if (slot->present & SNDRV_MIXER_OSS_PRESENT_GROUTE) { + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_GROUTE], left, right, 1); + } + } + return 0; +} + +static int snd_mixer_oss_get_recsrc1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int *active) +{ + struct slot *slot = pslot->private_data; + int left, right; + + left = right = 1; + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], &left, &right, 0); + *active = (left || right) ? 1 : 0; + return 0; +} + +static int snd_mixer_oss_get_recsrc1_route(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int *active) +{ + struct slot *slot = pslot->private_data; + int left, right; + + left = right = 1; + snd_mixer_oss_get_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], &left, &right, 1); + *active = (left || right) ? 1 : 0; + return 0; +} + +static int snd_mixer_oss_put_recsrc1_sw(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int active) +{ + struct slot *slot = pslot->private_data; + + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CSWITCH], active, active, 0); + return 0; +} + +static int snd_mixer_oss_put_recsrc1_route(struct snd_mixer_oss_file *fmixer, + struct snd_mixer_oss_slot *pslot, + int active) +{ + struct slot *slot = pslot->private_data; + + snd_mixer_oss_put_volume1_sw(fmixer, pslot, slot->numid[SNDRV_MIXER_OSS_ITEM_CROUTE], active, active, 1); + return 0; +} + +static int snd_mixer_oss_get_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned int *active_index) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_kcontrol *kctl; + struct snd_mixer_oss_slot *pslot; + struct slot *slot; + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + int err, idx; + + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) { + err = -ENOMEM; + goto __free_only; + } + down_read(&card->controls_rwsem); + kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); + if (! kctl) { + err = -ENOENT; + goto __unlock; + } + err = kctl->info(kctl, uinfo); + if (err < 0) + goto __unlock; + err = kctl->get(kctl, uctl); + if (err < 0) + goto __unlock; + for (idx = 0; idx < 32; idx++) { + if (!(mixer->mask_recsrc & (1 << idx))) + continue; + pslot = &mixer->slots[idx]; + slot = pslot->private_data; + if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) + continue; + if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) + continue; + if (slot->capture_item == uctl->value.enumerated.item[0]) { + *active_index = idx; + break; + } + } + err = 0; + __unlock: + up_read(&card->controls_rwsem); + __free_only: + kfree(uctl); + kfree(uinfo); + return err; +} + +static int snd_mixer_oss_put_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned int active_index) +{ + struct snd_card *card = fmixer->card; + struct snd_mixer_oss *mixer = fmixer->mixer; + struct snd_kcontrol *kctl; + struct snd_mixer_oss_slot *pslot; + struct slot *slot = NULL; + struct snd_ctl_elem_info *uinfo; + struct snd_ctl_elem_value *uctl; + int err; + unsigned int idx; + + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (uinfo == NULL || uctl == NULL) { + err = -ENOMEM; + goto __free_only; + } + down_read(&card->controls_rwsem); + kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); + if (! kctl) { + err = -ENOENT; + goto __unlock; + } + err = kctl->info(kctl, uinfo); + if (err < 0) + goto __unlock; + for (idx = 0; idx < 32; idx++) { + if (!(mixer->mask_recsrc & (1 << idx))) + continue; + pslot = &mixer->slots[idx]; + slot = pslot->private_data; + if (slot->signature != SNDRV_MIXER_OSS_SIGNATURE) + continue; + if (!(slot->present & SNDRV_MIXER_OSS_PRESENT_CAPTURE)) + continue; + if (idx == active_index) + break; + slot = NULL; + } + if (! slot) + goto __unlock; + for (idx = 0; idx < uinfo->count; idx++) + uctl->value.enumerated.item[idx] = slot->capture_item; + err = kctl->put(kctl, uctl); + if (err > 0) + snd_ctl_notify(fmixer->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + err = 0; + __unlock: + up_read(&card->controls_rwsem); + __free_only: + kfree(uctl); + kfree(uinfo); + return err; +} + +struct snd_mixer_oss_assign_table { + int oss_id; + const char *name; + int index; +}; + +static int snd_mixer_oss_build_test(struct snd_mixer_oss *mixer, struct slot *slot, const char *name, int index, int item) +{ + struct snd_ctl_elem_info *info; + struct snd_kcontrol *kcontrol; + struct snd_card *card = mixer->card; + int err; + + down_read(&card->controls_rwsem); + kcontrol = snd_mixer_oss_test_id(mixer, name, index); + if (kcontrol == NULL) { + up_read(&card->controls_rwsem); + return 0; + } + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) { + up_read(&card->controls_rwsem); + return -ENOMEM; + } + err = kcontrol->info(kcontrol, info); + if (err < 0) { + up_read(&card->controls_rwsem); + kfree(info); + return err; + } + slot->numid[item] = kcontrol->id.numid; + up_read(&card->controls_rwsem); + if (info->count > slot->channels) + slot->channels = info->count; + slot->present |= 1 << item; + kfree(info); + return 0; +} + +static void snd_mixer_oss_slot_free(struct snd_mixer_oss_slot *chn) +{ + struct slot *p = chn->private_data; + if (p) { + if (p->allocated && p->assigned) { + kfree_const(p->assigned->name); + kfree_const(p->assigned); + } + kfree(p); + } +} + +static void mixer_slot_clear(struct snd_mixer_oss_slot *rslot) +{ + int idx = rslot->number; /* remember this */ + if (rslot->private_free) + rslot->private_free(rslot); + memset(rslot, 0, sizeof(*rslot)); + rslot->number = idx; +} + +/* In a separate function to keep gcc 3.2 happy - do NOT merge this in + snd_mixer_oss_build_input! */ +static int snd_mixer_oss_build_test_all(struct snd_mixer_oss *mixer, + const struct snd_mixer_oss_assign_table *ptr, + struct slot *slot) +{ + char str[64]; + int err; + + err = snd_mixer_oss_build_test(mixer, slot, ptr->name, ptr->index, + SNDRV_MIXER_OSS_ITEM_GLOBAL); + if (err) + return err; + sprintf(str, "%s Switch", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GSWITCH); + if (err) + return err; + sprintf(str, "%s Route", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GROUTE); + if (err) + return err; + sprintf(str, "%s Volume", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_GVOLUME); + if (err) + return err; + sprintf(str, "%s Playback Switch", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PSWITCH); + if (err) + return err; + sprintf(str, "%s Playback Route", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PROUTE); + if (err) + return err; + sprintf(str, "%s Playback Volume", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_PVOLUME); + if (err) + return err; + sprintf(str, "%s Capture Switch", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CSWITCH); + if (err) + return err; + sprintf(str, "%s Capture Route", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CROUTE); + if (err) + return err; + sprintf(str, "%s Capture Volume", ptr->name); + err = snd_mixer_oss_build_test(mixer, slot, str, ptr->index, + SNDRV_MIXER_OSS_ITEM_CVOLUME); + if (err) + return err; + + return 0; +} + +/* + * build an OSS mixer element. + * ptr_allocated means the entry is dynamically allocated (change via proc file). + * when replace_old = 1, the old entry is replaced with the new one. + */ +static int snd_mixer_oss_build_input(struct snd_mixer_oss *mixer, + const struct snd_mixer_oss_assign_table *ptr, + int ptr_allocated, int replace_old) +{ + struct slot slot; + struct slot *pslot; + struct snd_kcontrol *kctl; + struct snd_mixer_oss_slot *rslot; + char str[64]; + + /* check if already assigned */ + if (mixer->slots[ptr->oss_id].get_volume && ! replace_old) + return 0; + + memset(&slot, 0, sizeof(slot)); + memset(slot.numid, 0xff, sizeof(slot.numid)); /* ID_UNKNOWN */ + if (snd_mixer_oss_build_test_all(mixer, ptr, &slot)) + return 0; + down_read(&mixer->card->controls_rwsem); + kctl = NULL; + if (!ptr->index) + kctl = snd_mixer_oss_test_id(mixer, "Capture Source", 0); + if (kctl) { + struct snd_ctl_elem_info *uinfo; + + uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL); + if (! uinfo) { + up_read(&mixer->card->controls_rwsem); + return -ENOMEM; + } + + if (kctl->info(kctl, uinfo)) { + up_read(&mixer->card->controls_rwsem); + kfree(uinfo); + return 0; + } + strcpy(str, ptr->name); + if (!strcmp(str, "Master")) + strcpy(str, "Mix"); + if (!strcmp(str, "Master Mono")) + strcpy(str, "Mix Mono"); + slot.capture_item = 0; + if (!strcmp(uinfo->value.enumerated.name, str)) { + slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; + } else { + for (slot.capture_item = 1; slot.capture_item < uinfo->value.enumerated.items; slot.capture_item++) { + uinfo->value.enumerated.item = slot.capture_item; + if (kctl->info(kctl, uinfo)) { + up_read(&mixer->card->controls_rwsem); + kfree(uinfo); + return 0; + } + if (!strcmp(uinfo->value.enumerated.name, str)) { + slot.present |= SNDRV_MIXER_OSS_PRESENT_CAPTURE; + break; + } + } + } + kfree(uinfo); + } + up_read(&mixer->card->controls_rwsem); + if (slot.present != 0) { + pslot = kmalloc(sizeof(slot), GFP_KERNEL); + if (! pslot) + return -ENOMEM; + *pslot = slot; + pslot->signature = SNDRV_MIXER_OSS_SIGNATURE; + pslot->assigned = ptr; + pslot->allocated = ptr_allocated; + rslot = &mixer->slots[ptr->oss_id]; + mixer_slot_clear(rslot); + rslot->stereo = slot.channels > 1 ? 1 : 0; + rslot->get_volume = snd_mixer_oss_get_volume1; + rslot->put_volume = snd_mixer_oss_put_volume1; + /* note: ES18xx have both Capture Source and XX Capture Volume !!! */ + if (slot.present & SNDRV_MIXER_OSS_PRESENT_CSWITCH) { + rslot->get_recsrc = snd_mixer_oss_get_recsrc1_sw; + rslot->put_recsrc = snd_mixer_oss_put_recsrc1_sw; + } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CROUTE) { + rslot->get_recsrc = snd_mixer_oss_get_recsrc1_route; + rslot->put_recsrc = snd_mixer_oss_put_recsrc1_route; + } else if (slot.present & SNDRV_MIXER_OSS_PRESENT_CAPTURE) { + mixer->mask_recsrc |= 1 << ptr->oss_id; + } + rslot->private_data = pslot; + rslot->private_free = snd_mixer_oss_slot_free; + return 1; + } + return 0; +} + +#ifdef CONFIG_SND_PROC_FS +/* + */ +#define MIXER_VOL(name) [SOUND_MIXER_##name] = #name +static const char * const oss_mixer_names[SNDRV_OSS_MAX_MIXERS] = { + MIXER_VOL(VOLUME), + MIXER_VOL(BASS), + MIXER_VOL(TREBLE), + MIXER_VOL(SYNTH), + MIXER_VOL(PCM), + MIXER_VOL(SPEAKER), + MIXER_VOL(LINE), + MIXER_VOL(MIC), + MIXER_VOL(CD), + MIXER_VOL(IMIX), + MIXER_VOL(ALTPCM), + MIXER_VOL(RECLEV), + MIXER_VOL(IGAIN), + MIXER_VOL(OGAIN), + MIXER_VOL(LINE1), + MIXER_VOL(LINE2), + MIXER_VOL(LINE3), + MIXER_VOL(DIGITAL1), + MIXER_VOL(DIGITAL2), + MIXER_VOL(DIGITAL3), + MIXER_VOL(PHONEIN), + MIXER_VOL(PHONEOUT), + MIXER_VOL(VIDEO), + MIXER_VOL(RADIO), + MIXER_VOL(MONITOR), +}; + +/* + * /proc interface + */ + +static void snd_mixer_oss_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_mixer_oss *mixer = entry->private_data; + int i; + + mutex_lock(&mixer->reg_mutex); + for (i = 0; i < SNDRV_OSS_MAX_MIXERS; i++) { + struct slot *p; + + if (! oss_mixer_names[i]) + continue; + p = (struct slot *)mixer->slots[i].private_data; + snd_iprintf(buffer, "%s ", oss_mixer_names[i]); + if (p && p->assigned) + snd_iprintf(buffer, "\"%s\" %d\n", + p->assigned->name, + p->assigned->index); + else + snd_iprintf(buffer, "\"\" 0\n"); + } + mutex_unlock(&mixer->reg_mutex); +} + +static void snd_mixer_oss_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_mixer_oss *mixer = entry->private_data; + char line[128], str[32], idxstr[16]; + const char *cptr; + unsigned int idx; + int ch; + struct snd_mixer_oss_assign_table *tbl; + struct slot *slot; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + cptr = snd_info_get_str(str, line, sizeof(str)); + for (ch = 0; ch < SNDRV_OSS_MAX_MIXERS; ch++) + if (oss_mixer_names[ch] && strcmp(oss_mixer_names[ch], str) == 0) + break; + if (ch >= SNDRV_OSS_MAX_MIXERS) { + pr_err("ALSA: mixer_oss: invalid OSS volume '%s'\n", + str); + continue; + } + cptr = snd_info_get_str(str, cptr, sizeof(str)); + if (! *str) { + /* remove the entry */ + mutex_lock(&mixer->reg_mutex); + mixer_slot_clear(&mixer->slots[ch]); + mutex_unlock(&mixer->reg_mutex); + continue; + } + snd_info_get_str(idxstr, cptr, sizeof(idxstr)); + idx = simple_strtoul(idxstr, NULL, 10); + if (idx >= 0x4000) { /* too big */ + pr_err("ALSA: mixer_oss: invalid index %d\n", idx); + continue; + } + mutex_lock(&mixer->reg_mutex); + slot = (struct slot *)mixer->slots[ch].private_data; + if (slot && slot->assigned && + slot->assigned->index == idx && ! strcmp(slot->assigned->name, str)) + /* not changed */ + goto __unlock; + tbl = kmalloc(sizeof(*tbl), GFP_KERNEL); + if (!tbl) + goto __unlock; + tbl->oss_id = ch; + tbl->name = kstrdup(str, GFP_KERNEL); + if (! tbl->name) { + kfree(tbl); + goto __unlock; + } + tbl->index = idx; + if (snd_mixer_oss_build_input(mixer, tbl, 1, 1) <= 0) { + kfree(tbl->name); + kfree(tbl); + } + __unlock: + mutex_unlock(&mixer->reg_mutex); + } +} + +static void snd_mixer_oss_proc_init(struct snd_mixer_oss *mixer) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(mixer->card, "oss_mixer", + mixer->card->proc_root); + if (! entry) + return; + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->mode = S_IFREG | 0644; + entry->c.text.read = snd_mixer_oss_proc_read; + entry->c.text.write = snd_mixer_oss_proc_write; + entry->private_data = mixer; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + mixer->proc_entry = entry; +} + +static void snd_mixer_oss_proc_done(struct snd_mixer_oss *mixer) +{ + snd_info_free_entry(mixer->proc_entry); + mixer->proc_entry = NULL; +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_mixer_oss_proc_init(mix) +#define snd_mixer_oss_proc_done(mix) +#endif /* CONFIG_SND_PROC_FS */ + +static void snd_mixer_oss_build(struct snd_mixer_oss *mixer) +{ + static const struct snd_mixer_oss_assign_table table[] = { + { SOUND_MIXER_VOLUME, "Master", 0 }, + { SOUND_MIXER_VOLUME, "Front", 0 }, /* fallback */ + { SOUND_MIXER_BASS, "Tone Control - Bass", 0 }, + { SOUND_MIXER_TREBLE, "Tone Control - Treble", 0 }, + { SOUND_MIXER_SYNTH, "Synth", 0 }, + { SOUND_MIXER_SYNTH, "FM", 0 }, /* fallback */ + { SOUND_MIXER_SYNTH, "Music", 0 }, /* fallback */ + { SOUND_MIXER_PCM, "PCM", 0 }, + { SOUND_MIXER_SPEAKER, "Beep", 0 }, + { SOUND_MIXER_SPEAKER, "PC Speaker", 0 }, /* fallback */ + { SOUND_MIXER_SPEAKER, "Speaker", 0 }, /* fallback */ + { SOUND_MIXER_LINE, "Line", 0 }, + { SOUND_MIXER_MIC, "Mic", 0 }, + { SOUND_MIXER_CD, "CD", 0 }, + { SOUND_MIXER_IMIX, "Monitor Mix", 0 }, + { SOUND_MIXER_ALTPCM, "PCM", 1 }, + { SOUND_MIXER_ALTPCM, "Headphone", 0 }, /* fallback */ + { SOUND_MIXER_ALTPCM, "Wave", 0 }, /* fallback */ + { SOUND_MIXER_RECLEV, "-- nothing --", 0 }, + { SOUND_MIXER_IGAIN, "Capture", 0 }, + { SOUND_MIXER_OGAIN, "Playback", 0 }, + { SOUND_MIXER_LINE1, "Aux", 0 }, + { SOUND_MIXER_LINE2, "Aux", 1 }, + { SOUND_MIXER_LINE3, "Aux", 2 }, + { SOUND_MIXER_DIGITAL1, "Digital", 0 }, + { SOUND_MIXER_DIGITAL1, "IEC958", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL1, "IEC958 Optical", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL1, "IEC958 Coaxial", 0 }, /* fallback */ + { SOUND_MIXER_DIGITAL2, "Digital", 1 }, + { SOUND_MIXER_DIGITAL3, "Digital", 2 }, + { SOUND_MIXER_PHONEIN, "Phone", 0 }, + { SOUND_MIXER_PHONEOUT, "Master Mono", 0 }, + { SOUND_MIXER_PHONEOUT, "Speaker", 0 }, /*fallback*/ + { SOUND_MIXER_PHONEOUT, "Mono", 0 }, /*fallback*/ + { SOUND_MIXER_PHONEOUT, "Phone", 0 }, /* fallback */ + { SOUND_MIXER_VIDEO, "Video", 0 }, + { SOUND_MIXER_RADIO, "Radio", 0 }, + { SOUND_MIXER_MONITOR, "Monitor", 0 } + }; + unsigned int idx; + + for (idx = 0; idx < ARRAY_SIZE(table); idx++) + snd_mixer_oss_build_input(mixer, &table[idx], 0, 0); + if (mixer->mask_recsrc) { + mixer->get_recsrc = snd_mixer_oss_get_recsrc2; + mixer->put_recsrc = snd_mixer_oss_put_recsrc2; + } +} + +/* + * + */ + +static int snd_mixer_oss_free1(void *private) +{ + struct snd_mixer_oss *mixer = private; + struct snd_card *card; + int idx; + + if (!mixer) + return 0; + card = mixer->card; + if (snd_BUG_ON(mixer != card->mixer_oss)) + return -ENXIO; + card->mixer_oss = NULL; + for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) { + struct snd_mixer_oss_slot *chn = &mixer->slots[idx]; + if (chn->private_free) + chn->private_free(chn); + } + kfree(mixer); + return 0; +} + +static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) +{ + struct snd_mixer_oss *mixer; + + if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) { + int idx, err; + + mixer = kcalloc(2, sizeof(*mixer), GFP_KERNEL); + if (mixer == NULL) + return -ENOMEM; + mutex_init(&mixer->reg_mutex); + err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, + card, 0, + &snd_mixer_oss_f_ops, card); + if (err < 0) { + dev_err(card->dev, + "unable to register OSS mixer device %i:%i\n", + card->number, 0); + kfree(mixer); + return err; + } + mixer->oss_dev_alloc = 1; + mixer->card = card; + if (*card->mixername) + strscpy(mixer->name, card->mixername, sizeof(mixer->name)); + else + snprintf(mixer->name, sizeof(mixer->name), + "mixer%i", card->number); +#ifdef SNDRV_OSS_INFO_DEV_MIXERS + snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIXERS, + card->number, + mixer->name); +#endif + for (idx = 0; idx < SNDRV_OSS_MAX_MIXERS; idx++) + mixer->slots[idx].number = idx; + card->mixer_oss = mixer; + snd_mixer_oss_build(mixer); + snd_mixer_oss_proc_init(mixer); + } else { + mixer = card->mixer_oss; + if (mixer == NULL) + return 0; + if (mixer->oss_dev_alloc) { +#ifdef SNDRV_OSS_INFO_DEV_MIXERS + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIXERS, mixer->card->number); +#endif + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, mixer->card, 0); + mixer->oss_dev_alloc = 0; + } + if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) + return 0; + snd_mixer_oss_proc_done(mixer); + return snd_mixer_oss_free1(mixer); + } + return 0; +} + +static int __init alsa_mixer_oss_init(void) +{ + struct snd_card *card; + int idx; + + snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler; + for (idx = 0; idx < SNDRV_CARDS; idx++) { + card = snd_card_ref(idx); + if (card) { + snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_REGISTER); + snd_card_unref(card); + } + } + return 0; +} + +static void __exit alsa_mixer_oss_exit(void) +{ + struct snd_card *card; + int idx; + + snd_mixer_oss_notify_callback = NULL; + for (idx = 0; idx < SNDRV_CARDS; idx++) { + card = snd_card_ref(idx); + if (card) { + snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_FREE); + snd_card_unref(card); + } + } +} + +module_init(alsa_mixer_oss_init) +module_exit(alsa_mixer_oss_exit) diff --git a/sound/core/oss/mulaw.c b/sound/core/oss/mulaw.c new file mode 100644 index 0000000000..fe27034f28 --- /dev/null +++ b/sound/core/oss/mulaw.c @@ -0,0 +1,346 @@ +/* + * Mu-Law conversion Plug-In Interface + * Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> + * Uros Bizjak <uros@kss-loka.si> + * + * Based on reference implementation by Sun Microsystems, Inc. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +#define SIGN_BIT (0x80) /* Sign bit for a u-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of u-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +static inline int val_seg(int val) +{ + int r = 0; + val >>= 7; + if (val & 0xf0) { + val >>= 4; + r += 4; + } + if (val & 0x0c) { + val >>= 2; + r += 2; + } + if (val & 0x02) + r += 1; + return r; +} + +#define BIAS (0x84) /* Bias for linear code. */ + +/* + * linear2ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +static unsigned char linear2ulaw(int pcm_val) /* 2's complement (16-bit range) */ +{ + int mask; + int seg; + unsigned char uval; + + /* Get the sign and the magnitude of the value. */ + if (pcm_val < 0) { + pcm_val = BIAS - pcm_val; + mask = 0x7F; + } else { + pcm_val += BIAS; + mask = 0xFF; + } + if (pcm_val > 0x7FFF) + pcm_val = 0x7FFF; + + /* Convert the scaled magnitude to segment number. */ + seg = val_seg(pcm_val); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF); + return uval ^ mask; +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +static int ulaw2linear(unsigned char u_val) +{ + int t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +/* + * Basic Mu-Law plugin + */ + +typedef void (*mulaw_f)(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames); + +struct mulaw_priv { + mulaw_f func; + int cvt_endian; /* need endian conversion? */ + unsigned int native_ofs; /* byte offset in native format */ + unsigned int copy_ofs; /* byte offset in s16 format */ + unsigned int native_bytes; /* byte size of the native format */ + unsigned int copy_bytes; /* bytes to copy per conversion */ + u16 flip; /* MSB flip for signedness, done after endian conversion */ +}; + +static inline void cvt_s16_to_native(struct mulaw_priv *data, + unsigned char *dst, u16 sample) +{ + sample ^= data->flip; + if (data->cvt_endian) + sample = swab16(sample); + if (data->native_bytes > data->copy_bytes) + memset(dst, 0, data->native_bytes); + memcpy(dst + data->native_ofs, (char *)&sample + data->copy_ofs, + data->copy_bytes); +} + +static void mulaw_decode(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + signed short sample = ulaw2linear(*src); + cvt_s16_to_native(data, dst, sample); + src += src_step; + dst += dst_step; + } + } +} + +static inline signed short cvt_native_to_s16(struct mulaw_priv *data, + unsigned char *src) +{ + u16 sample = 0; + memcpy((char *)&sample + data->copy_ofs, src + data->native_ofs, + data->copy_bytes); + if (data->cvt_endian) + sample = swab16(sample); + sample ^= data->flip; + return (signed short)sample; +} + +static void mulaw_encode(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct mulaw_priv *data = (struct mulaw_priv *)plugin->extra_data; + int channel; + int nchannels = plugin->src_format.channels; + for (channel = 0; channel < nchannels; ++channel) { + char *src; + char *dst; + int src_step, dst_step; + snd_pcm_uframes_t frames1; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = src_channels[channel].area.addr + src_channels[channel].area.first / 8; + dst = dst_channels[channel].area.addr + dst_channels[channel].area.first / 8; + src_step = src_channels[channel].area.step / 8; + dst_step = dst_channels[channel].area.step / 8; + frames1 = frames; + while (frames1-- > 0) { + signed short sample = cvt_native_to_s16(data, src); + *dst = linear2ulaw(sample); + src += src_step; + dst += dst_step; + } + } +} + +static snd_pcm_sframes_t mulaw_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + struct mulaw_priv *data; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + if (snd_BUG_ON(src_channels[channel].area.first % 8 || + src_channels[channel].area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels[channel].area.first % 8 || + dst_channels[channel].area.step % 8)) + return -ENXIO; + } + } +#endif + if (frames > dst_channels[0].frames) + frames = dst_channels[0].frames; + data = (struct mulaw_priv *)plugin->extra_data; + data->func(plugin, src_channels, dst_channels, frames); + return frames; +} + +static void init_data(struct mulaw_priv *data, snd_pcm_format_t format) +{ +#ifdef SNDRV_LITTLE_ENDIAN + data->cvt_endian = snd_pcm_format_big_endian(format) > 0; +#else + data->cvt_endian = snd_pcm_format_little_endian(format) > 0; +#endif + if (!snd_pcm_format_signed(format)) + data->flip = 0x8000; + data->native_bytes = snd_pcm_format_physical_width(format) / 8; + data->copy_bytes = data->native_bytes < 2 ? 1 : 2; + if (snd_pcm_format_little_endian(format)) { + data->native_ofs = data->native_bytes - data->copy_bytes; + data->copy_ofs = 2 - data->copy_bytes; + } else { + /* S24 in 4bytes need an 1 byte offset */ + data->native_ofs = data->native_bytes - + snd_pcm_format_width(format) / 8; + } +} + +int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct mulaw_priv *data; + struct snd_pcm_plugin *plugin; + struct snd_pcm_plugin_format *format; + mulaw_f func; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + + if (dst_format->format == SNDRV_PCM_FORMAT_MU_LAW) { + format = src_format; + func = mulaw_encode; + } + else if (src_format->format == SNDRV_PCM_FORMAT_MU_LAW) { + format = dst_format; + func = mulaw_decode; + } + else { + snd_BUG(); + return -EINVAL; + } + if (!snd_pcm_format_linear(format->format)) + return -EINVAL; + + err = snd_pcm_plugin_build(plug, "Mu-Law<->linear conversion", + src_format, dst_format, + sizeof(struct mulaw_priv), &plugin); + if (err < 0) + return err; + data = (struct mulaw_priv *)plugin->extra_data; + data->func = func; + init_data(data, format->format); + plugin->transfer = mulaw_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c new file mode 100644 index 0000000000..728c211142 --- /dev/null +++ b/sound/core/oss/pcm_oss.c @@ -0,0 +1,3244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Digital Audio (PCM) abstract layer / OSS compatible + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#if 0 +#define PLUGIN_DEBUG +#endif +#if 0 +#define OSS_DEBUG +#endif + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> +#include <linux/time.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/math64.h> +#include <linux/string.h> +#include <linux/compat.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "pcm_plugin.h" +#include <sound/info.h> +#include <linux/soundcard.h> +#include <sound/initval.h> +#include <sound/mixer_oss.h> + +#define OSS_ALSAEMULVER _SIOR ('M', 249, int) + +static int dsp_map[SNDRV_CARDS]; +static int adsp_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +static bool nonblock_open = 1; + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Abramo Bagnara <abramo@alsa-project.org>"); +MODULE_DESCRIPTION("PCM OSS emulation for ALSA."); +MODULE_LICENSE("GPL"); +module_param_array(dsp_map, int, NULL, 0444); +MODULE_PARM_DESC(dsp_map, "PCM device number assigned to 1st OSS device."); +module_param_array(adsp_map, int, NULL, 0444); +MODULE_PARM_DESC(adsp_map, "PCM device number assigned to 2nd OSS device."); +module_param(nonblock_open, bool, 0644); +MODULE_PARM_DESC(nonblock_open, "Don't block opening busy PCM devices."); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_PCM1); + +static int snd_pcm_oss_get_rate(struct snd_pcm_oss_file *pcm_oss_file); +static int snd_pcm_oss_get_channels(struct snd_pcm_oss_file *pcm_oss_file); +static int snd_pcm_oss_get_format(struct snd_pcm_oss_file *pcm_oss_file); + +/* + * helper functions to process hw_params + */ +static int snd_interval_refine_min(struct snd_interval *i, unsigned int min, int openmin) +{ + int changed = 0; + if (i->min < min) { + i->min = min; + i->openmin = openmin; + changed = 1; + } else if (i->min == min && !i->openmin && openmin) { + i->openmin = 1; + changed = 1; + } + if (i->integer) { + if (i->openmin) { + i->min++; + i->openmin = 0; + } + } + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} + +static int snd_interval_refine_max(struct snd_interval *i, unsigned int max, int openmax) +{ + int changed = 0; + if (i->max > max) { + i->max = max; + i->openmax = openmax; + changed = 1; + } else if (i->max == max && !i->openmax && openmax) { + i->openmax = 1; + changed = 1; + } + if (i->integer) { + if (i->openmax) { + i->max--; + i->openmax = 0; + } + } + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} + +static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) +{ + struct snd_interval t; + t.empty = 0; + t.min = t.max = val; + t.openmin = t.openmax = 0; + t.integer = 1; + return snd_interval_refine(i, &t); +} + +/** + * snd_pcm_hw_param_value_min + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Return the minimum value for field PAR. + */ +static unsigned int +snd_pcm_hw_param_value_min(const struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + if (dir) + *dir = 0; + return snd_mask_min(hw_param_mask_c(params, var)); + } + if (hw_is_interval(var)) { + const struct snd_interval *i = hw_param_interval_c(params, var); + if (dir) + *dir = i->openmin; + return snd_interval_min(i); + } + return -EINVAL; +} + +/** + * snd_pcm_hw_param_value_max + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Return the maximum value for field PAR. + */ +static int +snd_pcm_hw_param_value_max(const struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + if (dir) + *dir = 0; + return snd_mask_max(hw_param_mask_c(params, var)); + } + if (hw_is_interval(var)) { + const struct snd_interval *i = hw_param_interval_c(params, var); + if (dir) + *dir = - (int) i->openmax; + return snd_interval_max(i); + } + return -EINVAL; +} + +static int _snd_pcm_hw_param_mask(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, + const struct snd_mask *val) +{ + int changed; + changed = snd_mask_refine(hw_param_mask(params, var), val); + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +static int snd_pcm_hw_param_mask(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, + const struct snd_mask *val) +{ + int changed = _snd_pcm_hw_param_mask(params, var, val); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return 0; +} + +static int _snd_pcm_hw_param_min(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + int open = 0; + if (dir) { + if (dir > 0) { + open = 1; + } else if (dir < 0) { + if (val > 0) { + open = 1; + val--; + } + } + } + if (hw_is_mask(var)) + changed = snd_mask_refine_min(hw_param_mask(params, var), + val + !!open); + else if (hw_is_interval(var)) + changed = snd_interval_refine_min(hw_param_interval(params, var), + val, open); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_min + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @val: minimal value + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS remove from PAR all + * values < VAL. Reduce configuration space accordingly. + * Return new minimum or -EINVAL if the configuration space is empty + */ +static int snd_pcm_hw_param_min(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int *dir) +{ + int changed = _snd_pcm_hw_param_min(params, var, val, dir ? *dir : 0); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value_min(params, var, dir); +} + +static int _snd_pcm_hw_param_max(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + int open = 0; + if (dir) { + if (dir < 0) { + open = 1; + } else if (dir > 0) { + open = 1; + val++; + } + } + if (hw_is_mask(var)) { + if (val == 0 && open) { + snd_mask_none(hw_param_mask(params, var)); + changed = -EINVAL; + } else + changed = snd_mask_refine_max(hw_param_mask(params, var), + val - !!open); + } else if (hw_is_interval(var)) + changed = snd_interval_refine_max(hw_param_interval(params, var), + val, open); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_max + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @val: maximal value + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS remove from PAR all + * values >= VAL + 1. Reduce configuration space accordingly. + * Return new maximum or -EINVAL if the configuration space is empty + */ +static int snd_pcm_hw_param_max(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int *dir) +{ + int changed = _snd_pcm_hw_param_max(params, var, val, dir ? *dir : 0); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value_max(params, var, dir); +} + +static int boundary_sub(int a, int adir, + int b, int bdir, + int *c, int *cdir) +{ + adir = adir < 0 ? -1 : (adir > 0 ? 1 : 0); + bdir = bdir < 0 ? -1 : (bdir > 0 ? 1 : 0); + *c = a - b; + *cdir = adir - bdir; + if (*cdir == -2) { + (*c)--; + } else if (*cdir == 2) { + (*c)++; + } + return 0; +} + +static int boundary_lt(unsigned int a, int adir, + unsigned int b, int bdir) +{ + if (adir < 0) { + a--; + adir = 1; + } else if (adir > 0) + adir = 1; + if (bdir < 0) { + b--; + bdir = 1; + } else if (bdir > 0) + bdir = 1; + return a < b || (a == b && adir < bdir); +} + +/* Return 1 if min is nearer to best than max */ +static int boundary_nearer(int min, int mindir, + int best, int bestdir, + int max, int maxdir) +{ + int dmin, dmindir; + int dmax, dmaxdir; + boundary_sub(best, bestdir, min, mindir, &dmin, &dmindir); + boundary_sub(max, maxdir, best, bestdir, &dmax, &dmaxdir); + return boundary_lt(dmin, dmindir, dmax, dmaxdir); +} + +/** + * snd_pcm_hw_param_near + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @best: value to set + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS set PAR to the available value + * nearest to VAL. Reduce configuration space accordingly. + * This function cannot be called for SNDRV_PCM_HW_PARAM_ACCESS, + * SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_SUBFORMAT. + * Return the value found. + */ +static int snd_pcm_hw_param_near(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int best, + int *dir) +{ + struct snd_pcm_hw_params *save = NULL; + int v; + unsigned int saved_min; + int last = 0; + int min, max; + int mindir, maxdir; + int valdir = dir ? *dir : 0; + /* FIXME */ + if (best > INT_MAX) + best = INT_MAX; + min = max = best; + mindir = maxdir = valdir; + if (maxdir > 0) + maxdir = 0; + else if (maxdir == 0) + maxdir = -1; + else { + maxdir = 1; + max--; + } + save = kmalloc(sizeof(*save), GFP_KERNEL); + if (save == NULL) + return -ENOMEM; + *save = *params; + saved_min = min; + min = snd_pcm_hw_param_min(pcm, params, var, min, &mindir); + if (min >= 0) { + struct snd_pcm_hw_params *params1; + if (max < 0) + goto _end; + if ((unsigned int)min == saved_min && mindir == valdir) + goto _end; + params1 = kmalloc(sizeof(*params1), GFP_KERNEL); + if (params1 == NULL) { + kfree(save); + return -ENOMEM; + } + *params1 = *save; + max = snd_pcm_hw_param_max(pcm, params1, var, max, &maxdir); + if (max < 0) { + kfree(params1); + goto _end; + } + if (boundary_nearer(max, maxdir, best, valdir, min, mindir)) { + *params = *params1; + last = 1; + } + kfree(params1); + } else { + *params = *save; + max = snd_pcm_hw_param_max(pcm, params, var, max, &maxdir); + if (max < 0) { + kfree(save); + return max; + } + last = 1; + } + _end: + kfree(save); + if (last) + v = snd_pcm_hw_param_last(pcm, params, var, dir); + else + v = snd_pcm_hw_param_first(pcm, params, var, dir); + return v; +} + +static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + if (hw_is_mask(var)) { + struct snd_mask *m = hw_param_mask(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_mask_none(m); + } else { + if (dir > 0) + val++; + else if (dir < 0) + val--; + changed = snd_mask_refine_set(hw_param_mask(params, var), val); + } + } else if (hw_is_interval(var)) { + struct snd_interval *i = hw_param_interval(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_interval_none(i); + } else if (dir == 0) + changed = snd_interval_refine_set(i, val); + else { + struct snd_interval t; + t.openmin = 1; + t.openmax = 1; + t.empty = 0; + t.integer = 0; + if (dir < 0) { + t.min = val - 1; + t.max = val; + } else { + t.min = val; + t.max = val+1; + } + changed = snd_interval_refine(i, &t); + } + } else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/** + * snd_pcm_hw_param_set + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @val: value to set + * @dir: pointer to the direction (-1,0,1) or NULL + * + * Inside configuration space defined by PARAMS remove from PAR all + * values != VAL. Reduce configuration space accordingly. + * Return VAL or -EINVAL if the configuration space is empty + */ +static int snd_pcm_hw_param_set(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed = _snd_pcm_hw_param_set(params, var, val, dir); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value(params, var, NULL); +} + +static int _snd_pcm_hw_param_setinteger(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + int changed; + changed = snd_interval_setinteger(hw_param_interval(params, var)); + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + +/* + * plugin + */ + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS +static int snd_pcm_oss_plugin_clear(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_plugin *plugin, *next; + + plugin = runtime->oss.plugin_first; + while (plugin) { + next = plugin->next; + snd_pcm_plugin_free(plugin); + plugin = next; + } + runtime->oss.plugin_first = runtime->oss.plugin_last = NULL; + return 0; +} + +static int snd_pcm_plugin_insert(struct snd_pcm_plugin *plugin) +{ + struct snd_pcm_runtime *runtime = plugin->plug->runtime; + plugin->next = runtime->oss.plugin_first; + plugin->prev = NULL; + if (runtime->oss.plugin_first) { + runtime->oss.plugin_first->prev = plugin; + runtime->oss.plugin_first = plugin; + } else { + runtime->oss.plugin_last = + runtime->oss.plugin_first = plugin; + } + return 0; +} + +int snd_pcm_plugin_append(struct snd_pcm_plugin *plugin) +{ + struct snd_pcm_runtime *runtime = plugin->plug->runtime; + plugin->next = NULL; + plugin->prev = runtime->oss.plugin_last; + if (runtime->oss.plugin_last) { + runtime->oss.plugin_last->next = plugin; + runtime->oss.plugin_last = plugin; + } else { + runtime->oss.plugin_last = + runtime->oss.plugin_first = plugin; + } + return 0; +} +#endif /* CONFIG_SND_PCM_OSS_PLUGINS */ + +static long snd_pcm_oss_bytes(struct snd_pcm_substream *substream, long frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + long buffer_size = snd_pcm_lib_buffer_bytes(substream); + long bytes = frames_to_bytes(runtime, frames); + if (buffer_size == runtime->oss.buffer_bytes) + return bytes; +#if BITS_PER_LONG >= 64 + return runtime->oss.buffer_bytes * bytes / buffer_size; +#else + { + u64 bsize = (u64)runtime->oss.buffer_bytes * (u64)bytes; + return div_u64(bsize, buffer_size); + } +#endif +} + +static long snd_pcm_alsa_frames(struct snd_pcm_substream *substream, long bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + long buffer_size = snd_pcm_lib_buffer_bytes(substream); + if (buffer_size == runtime->oss.buffer_bytes) + return bytes_to_frames(runtime, bytes); + return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes); +} + +static inline +snd_pcm_uframes_t get_hw_ptr_period(struct snd_pcm_runtime *runtime) +{ + return runtime->hw_ptr_interrupt; +} + +/* define extended formats in the recent OSS versions (if any) */ +/* linear formats */ +#define AFMT_S32_LE 0x00001000 +#define AFMT_S32_BE 0x00002000 +#define AFMT_S24_LE 0x00008000 +#define AFMT_S24_BE 0x00010000 +#define AFMT_S24_PACKED 0x00040000 + +/* other supported formats */ +#define AFMT_FLOAT 0x00004000 +#define AFMT_SPDIF_RAW 0x00020000 + +/* unsupported formats */ +#define AFMT_AC3 0x00000400 +#define AFMT_VORBIS 0x00000800 + +static snd_pcm_format_t snd_pcm_oss_format_from(int format) +{ + switch (format) { + case AFMT_MU_LAW: return SNDRV_PCM_FORMAT_MU_LAW; + case AFMT_A_LAW: return SNDRV_PCM_FORMAT_A_LAW; + case AFMT_IMA_ADPCM: return SNDRV_PCM_FORMAT_IMA_ADPCM; + case AFMT_U8: return SNDRV_PCM_FORMAT_U8; + case AFMT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE; + case AFMT_S16_BE: return SNDRV_PCM_FORMAT_S16_BE; + case AFMT_S8: return SNDRV_PCM_FORMAT_S8; + case AFMT_U16_LE: return SNDRV_PCM_FORMAT_U16_LE; + case AFMT_U16_BE: return SNDRV_PCM_FORMAT_U16_BE; + case AFMT_MPEG: return SNDRV_PCM_FORMAT_MPEG; + case AFMT_S32_LE: return SNDRV_PCM_FORMAT_S32_LE; + case AFMT_S32_BE: return SNDRV_PCM_FORMAT_S32_BE; + case AFMT_S24_LE: return SNDRV_PCM_FORMAT_S24_LE; + case AFMT_S24_BE: return SNDRV_PCM_FORMAT_S24_BE; + case AFMT_S24_PACKED: return SNDRV_PCM_FORMAT_S24_3LE; + case AFMT_FLOAT: return SNDRV_PCM_FORMAT_FLOAT; + case AFMT_SPDIF_RAW: return SNDRV_PCM_FORMAT_IEC958_SUBFRAME; + default: return SNDRV_PCM_FORMAT_U8; + } +} + +static int snd_pcm_oss_format_to(snd_pcm_format_t format) +{ + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW; + case SNDRV_PCM_FORMAT_A_LAW: return AFMT_A_LAW; + case SNDRV_PCM_FORMAT_IMA_ADPCM: return AFMT_IMA_ADPCM; + case SNDRV_PCM_FORMAT_U8: return AFMT_U8; + case SNDRV_PCM_FORMAT_S16_LE: return AFMT_S16_LE; + case SNDRV_PCM_FORMAT_S16_BE: return AFMT_S16_BE; + case SNDRV_PCM_FORMAT_S8: return AFMT_S8; + case SNDRV_PCM_FORMAT_U16_LE: return AFMT_U16_LE; + case SNDRV_PCM_FORMAT_U16_BE: return AFMT_U16_BE; + case SNDRV_PCM_FORMAT_MPEG: return AFMT_MPEG; + case SNDRV_PCM_FORMAT_S32_LE: return AFMT_S32_LE; + case SNDRV_PCM_FORMAT_S32_BE: return AFMT_S32_BE; + case SNDRV_PCM_FORMAT_S24_LE: return AFMT_S24_LE; + case SNDRV_PCM_FORMAT_S24_BE: return AFMT_S24_BE; + case SNDRV_PCM_FORMAT_S24_3LE: return AFMT_S24_PACKED; + case SNDRV_PCM_FORMAT_FLOAT: return AFMT_FLOAT; + case SNDRV_PCM_FORMAT_IEC958_SUBFRAME: return AFMT_SPDIF_RAW; + default: return -EINVAL; + } +} + +static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *oss_params, + struct snd_pcm_hw_params *slave_params) +{ + ssize_t s; + ssize_t oss_buffer_size; + ssize_t oss_period_size, oss_periods; + ssize_t min_period_size, max_period_size; + struct snd_pcm_runtime *runtime = substream->runtime; + size_t oss_frame_size; + + oss_frame_size = snd_pcm_format_physical_width(params_format(oss_params)) * + params_channels(oss_params) / 8; + + oss_buffer_size = snd_pcm_hw_param_value_max(slave_params, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + NULL); + if (oss_buffer_size <= 0) + return -EINVAL; + oss_buffer_size = snd_pcm_plug_client_size(substream, + oss_buffer_size * oss_frame_size); + if (oss_buffer_size <= 0) + return -EINVAL; + oss_buffer_size = rounddown_pow_of_two(oss_buffer_size); + if (atomic_read(&substream->mmap_count)) { + if (oss_buffer_size > runtime->oss.mmap_bytes) + oss_buffer_size = runtime->oss.mmap_bytes; + } + + if (substream->oss.setup.period_size > 16) + oss_period_size = substream->oss.setup.period_size; + else if (runtime->oss.fragshift) { + oss_period_size = 1 << runtime->oss.fragshift; + if (oss_period_size > oss_buffer_size / 2) + oss_period_size = oss_buffer_size / 2; + } else { + int sd; + size_t bytes_per_sec = params_rate(oss_params) * snd_pcm_format_physical_width(params_format(oss_params)) * params_channels(oss_params) / 8; + + oss_period_size = oss_buffer_size; + do { + oss_period_size /= 2; + } while (oss_period_size > bytes_per_sec); + if (runtime->oss.subdivision == 0) { + sd = 4; + if (oss_period_size / sd > 4096) + sd *= 2; + if (oss_period_size / sd < 4096) + sd = 1; + } else + sd = runtime->oss.subdivision; + oss_period_size /= sd; + if (oss_period_size < 16) + oss_period_size = 16; + } + + min_period_size = snd_pcm_plug_client_size(substream, + snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); + if (min_period_size > 0) { + min_period_size *= oss_frame_size; + min_period_size = roundup_pow_of_two(min_period_size); + if (oss_period_size < min_period_size) + oss_period_size = min_period_size; + } + + max_period_size = snd_pcm_plug_client_size(substream, + snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); + if (max_period_size > 0) { + max_period_size *= oss_frame_size; + max_period_size = rounddown_pow_of_two(max_period_size); + if (oss_period_size > max_period_size) + oss_period_size = max_period_size; + } + + oss_periods = oss_buffer_size / oss_period_size; + + if (substream->oss.setup.periods > 1) + oss_periods = substream->oss.setup.periods; + + s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); + if (s > 0 && runtime->oss.maxfrags && s > runtime->oss.maxfrags) + s = runtime->oss.maxfrags; + if (oss_periods > s) + oss_periods = s; + + s = snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); + if (s < 2) + s = 2; + if (oss_periods < s) + oss_periods = s; + + while (oss_period_size * oss_periods > oss_buffer_size) + oss_period_size /= 2; + + if (oss_period_size < 16) + return -EINVAL; + + /* don't allocate too large period; 1MB period must be enough */ + if (oss_period_size > 1024 * 1024) + return -ENOMEM; + + runtime->oss.period_bytes = oss_period_size; + runtime->oss.period_frames = 1; + runtime->oss.periods = oss_periods; + return 0; +} + +static int choose_rate(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, unsigned int best_rate) +{ + const struct snd_interval *it; + struct snd_pcm_hw_params *save; + unsigned int rate, prev; + + save = kmalloc(sizeof(*save), GFP_KERNEL); + if (save == NULL) + return -ENOMEM; + *save = *params; + it = hw_param_interval_c(save, SNDRV_PCM_HW_PARAM_RATE); + + /* try multiples of the best rate */ + rate = best_rate; + for (;;) { + if (it->max < rate || (it->max == rate && it->openmax)) + break; + if (it->min < rate || (it->min == rate && !it->openmin)) { + int ret; + ret = snd_pcm_hw_param_set(substream, params, + SNDRV_PCM_HW_PARAM_RATE, + rate, 0); + if (ret == (int)rate) { + kfree(save); + return rate; + } + *params = *save; + } + prev = rate; + rate += best_rate; + if (rate <= prev) + break; + } + + /* not found, use the nearest rate */ + kfree(save); + return snd_pcm_hw_param_near(substream, params, SNDRV_PCM_HW_PARAM_RATE, best_rate, NULL); +} + +/* parameter locking: returns immediately if tried during streaming */ +static int lock_params(struct snd_pcm_runtime *runtime) +{ + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + if (atomic_read(&runtime->oss.rw_ref)) { + mutex_unlock(&runtime->oss.params_lock); + return -EBUSY; + } + return 0; +} + +static void unlock_params(struct snd_pcm_runtime *runtime) +{ + mutex_unlock(&runtime->oss.params_lock); +} + +static void snd_pcm_oss_release_buffers(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + kvfree(runtime->oss.buffer); + runtime->oss.buffer = NULL; +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + snd_pcm_oss_plugin_clear(substream); +#endif +} + +/* call with params_lock held */ +static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hw_params *params, *sparams; + struct snd_pcm_sw_params *sw_params; + ssize_t oss_buffer_size, oss_period_size; + size_t oss_frame_size; + int err; + int direct; + snd_pcm_format_t format, sformat; + int n; + const struct snd_mask *sformat_mask; + struct snd_mask mask; + + if (!runtime->oss.params) + return 0; + sw_params = kzalloc(sizeof(*sw_params), GFP_KERNEL); + params = kmalloc(sizeof(*params), GFP_KERNEL); + sparams = kmalloc(sizeof(*sparams), GFP_KERNEL); + if (!sw_params || !params || !sparams) { + err = -ENOMEM; + goto failure; + } + + if (atomic_read(&substream->mmap_count)) + direct = 1; + else + direct = substream->oss.setup.direct; + + _snd_pcm_hw_params_any(sparams); + _snd_pcm_hw_param_setinteger(sparams, SNDRV_PCM_HW_PARAM_PERIODS); + _snd_pcm_hw_param_min(sparams, SNDRV_PCM_HW_PARAM_PERIODS, 2, 0); + snd_mask_none(&mask); + if (atomic_read(&substream->mmap_count)) + snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + else { + snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (!direct) + snd_mask_set(&mask, (__force int)SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + } + err = snd_pcm_hw_param_mask(substream, sparams, SNDRV_PCM_HW_PARAM_ACCESS, &mask); + if (err < 0) { + pcm_dbg(substream->pcm, "No usable accesses\n"); + err = -EINVAL; + goto failure; + } + + err = choose_rate(substream, sparams, runtime->oss.rate); + if (err < 0) + goto failure; + err = snd_pcm_hw_param_near(substream, sparams, + SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, NULL); + if (err < 0) + goto failure; + + format = snd_pcm_oss_format_from(runtime->oss.format); + + sformat_mask = hw_param_mask_c(sparams, SNDRV_PCM_HW_PARAM_FORMAT); + if (direct) + sformat = format; + else + sformat = snd_pcm_plug_slave_format(format, sformat_mask); + + if ((__force int)sformat < 0 || + !snd_mask_test_format(sformat_mask, sformat)) { + pcm_for_each_format(sformat) { + if (snd_mask_test_format(sformat_mask, sformat) && + snd_pcm_oss_format_to(sformat) >= 0) + goto format_found; + } + pcm_dbg(substream->pcm, "Cannot find a format!!!\n"); + err = -EINVAL; + goto failure; + } + format_found: + err = _snd_pcm_hw_param_set(sparams, SNDRV_PCM_HW_PARAM_FORMAT, (__force int)sformat, 0); + if (err < 0) + goto failure; + + if (direct) { + memcpy(params, sparams, sizeof(*params)); + } else { + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, + (__force int)SNDRV_PCM_ACCESS_RW_INTERLEAVED, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, + (__force int)snd_pcm_oss_format_from(runtime->oss.format), 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, + runtime->oss.rate, 0); + pdprintf("client: access = %i, format = %i, channels = %i, rate = %i\n", + params_access(params), params_format(params), + params_channels(params), params_rate(params)); + } + pdprintf("slave: access = %i, format = %i, channels = %i, rate = %i\n", + params_access(sparams), params_format(sparams), + params_channels(sparams), params_rate(sparams)); + + oss_frame_size = snd_pcm_format_physical_width(params_format(params)) * + params_channels(params) / 8; + + err = snd_pcm_oss_period_size(substream, params, sparams); + if (err < 0) + goto failure; + + n = snd_pcm_plug_slave_size(substream, runtime->oss.period_bytes / oss_frame_size); + err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, n, NULL); + if (err < 0) + goto failure; + + err = snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_PERIODS, + runtime->oss.periods, NULL); + if (err < 0) + goto failure; + + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, sparams); + if (err < 0) { + pcm_dbg(substream->pcm, "HW_PARAMS failed: %i\n", err); + goto failure; + } + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + snd_pcm_oss_plugin_clear(substream); + if (!direct) { + /* add necessary plugins */ + err = snd_pcm_plug_format_plugins(substream, params, sparams); + if (err < 0) { + pcm_dbg(substream->pcm, + "snd_pcm_plug_format_plugins failed: %i\n", err); + goto failure; + } + if (runtime->oss.plugin_first) { + struct snd_pcm_plugin *plugin; + err = snd_pcm_plugin_build_io(substream, sparams, &plugin); + if (err < 0) { + pcm_dbg(substream->pcm, + "snd_pcm_plugin_build_io failed: %i\n", err); + goto failure; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_plugin_append(plugin); + } else { + err = snd_pcm_plugin_insert(plugin); + } + if (err < 0) + goto failure; + } + } +#endif + + if (runtime->oss.trigger) { + sw_params->start_threshold = 1; + } else { + sw_params->start_threshold = runtime->boundary; + } + if (atomic_read(&substream->mmap_count) || + substream->stream == SNDRV_PCM_STREAM_CAPTURE) + sw_params->stop_threshold = runtime->boundary; + else + sw_params->stop_threshold = runtime->buffer_size; + sw_params->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; + sw_params->period_step = 1; + sw_params->avail_min = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + 1 : runtime->period_size; + if (atomic_read(&substream->mmap_count) || + substream->oss.setup.nosilence) { + sw_params->silence_threshold = 0; + sw_params->silence_size = 0; + } else { + snd_pcm_uframes_t frames; + frames = runtime->period_size + 16; + if (frames > runtime->buffer_size) + frames = runtime->buffer_size; + sw_params->silence_threshold = frames; + sw_params->silence_size = frames; + } + + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_SW_PARAMS, sw_params); + if (err < 0) { + pcm_dbg(substream->pcm, "SW_PARAMS failed: %i\n", err); + goto failure; + } + + runtime->oss.periods = params_periods(sparams); + oss_period_size = snd_pcm_plug_client_size(substream, params_period_size(sparams)); + if (oss_period_size < 0) { + err = -EINVAL; + goto failure; + } +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + if (runtime->oss.plugin_first) { + err = snd_pcm_plug_alloc(substream, oss_period_size); + if (err < 0) + goto failure; + } +#endif + oss_period_size = array_size(oss_period_size, oss_frame_size); + oss_buffer_size = array_size(oss_period_size, runtime->oss.periods); + if (oss_buffer_size <= 0) { + err = -EINVAL; + goto failure; + } + + runtime->oss.period_bytes = oss_period_size; + runtime->oss.buffer_bytes = oss_buffer_size; + + pdprintf("oss: period bytes = %i, buffer bytes = %i\n", + runtime->oss.period_bytes, + runtime->oss.buffer_bytes); + pdprintf("slave: period_size = %i, buffer_size = %i\n", + params_period_size(sparams), + params_buffer_size(sparams)); + + runtime->oss.format = snd_pcm_oss_format_to(params_format(params)); + runtime->oss.channels = params_channels(params); + runtime->oss.rate = params_rate(params); + + kvfree(runtime->oss.buffer); + runtime->oss.buffer = kvzalloc(runtime->oss.period_bytes, GFP_KERNEL); + if (!runtime->oss.buffer) { + err = -ENOMEM; + goto failure; + } + + runtime->oss.params = 0; + runtime->oss.prepare = 1; + runtime->oss.buffer_used = 0; + if (runtime->dma_area) + snd_pcm_format_set_silence(runtime->format, runtime->dma_area, bytes_to_samples(runtime, runtime->dma_bytes)); + + runtime->oss.period_frames = snd_pcm_alsa_frames(substream, oss_period_size); + + err = 0; +failure: + if (err) + snd_pcm_oss_release_buffers(substream); + kfree(sw_params); + kfree(params); + kfree(sparams); + return err; +} + +/* this one takes the lock by itself */ +static int snd_pcm_oss_change_params(struct snd_pcm_substream *substream, + bool trylock) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + if (trylock) { + if (!(mutex_trylock(&runtime->oss.params_lock))) + return -EAGAIN; + } else if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + + err = snd_pcm_oss_change_params_locked(substream); + mutex_unlock(&runtime->oss.params_lock); + return err; +} + +static int snd_pcm_oss_get_active_substream(struct snd_pcm_oss_file *pcm_oss_file, struct snd_pcm_substream **r_substream) +{ + int idx, err; + struct snd_pcm_substream *asubstream = NULL, *substream; + + for (idx = 0; idx < 2; idx++) { + substream = pcm_oss_file->streams[idx]; + if (substream == NULL) + continue; + if (asubstream == NULL) + asubstream = substream; + if (substream->runtime->oss.params) { + err = snd_pcm_oss_change_params(substream, false); + if (err < 0) + return err; + } + } + if (!asubstream) + return -EIO; + if (r_substream) + *r_substream = asubstream; + return 0; +} + +/* call with params_lock held */ +/* NOTE: this always call PREPARE unconditionally no matter whether + * runtime->oss.prepare is set or not + */ +static int snd_pcm_oss_prepare(struct snd_pcm_substream *substream) +{ + int err; + struct snd_pcm_runtime *runtime = substream->runtime; + + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); + if (err < 0) { + pcm_dbg(substream->pcm, + "snd_pcm_oss_prepare: SNDRV_PCM_IOCTL_PREPARE failed\n"); + return err; + } + runtime->oss.prepare = 0; + runtime->oss.prev_hw_ptr_period = 0; + runtime->oss.period_ptr = 0; + runtime->oss.buffer_used = 0; + + return 0; +} + +static int snd_pcm_oss_make_ready(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int err; + + runtime = substream->runtime; + if (runtime->oss.params) { + err = snd_pcm_oss_change_params(substream, false); + if (err < 0) + return err; + } + if (runtime->oss.prepare) { + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + err = snd_pcm_oss_prepare(substream); + mutex_unlock(&runtime->oss.params_lock); + if (err < 0) + return err; + } + return 0; +} + +/* call with params_lock held */ +static int snd_pcm_oss_make_ready_locked(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int err; + + runtime = substream->runtime; + if (runtime->oss.params) { + err = snd_pcm_oss_change_params_locked(substream); + if (err < 0) + return err; + } + if (runtime->oss.prepare) { + err = snd_pcm_oss_prepare(substream); + if (err < 0) + return err; + } + return 0; +} + +static int snd_pcm_oss_capture_position_fixup(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay) +{ + struct snd_pcm_runtime *runtime; + snd_pcm_uframes_t frames; + int err = 0; + + while (1) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, delay); + if (err < 0) + break; + runtime = substream->runtime; + if (*delay <= (snd_pcm_sframes_t)runtime->buffer_size) + break; + /* in case of overrun, skip whole periods like OSS/Linux driver does */ + /* until avail(delay) <= buffer_size */ + frames = (*delay - runtime->buffer_size) + runtime->period_size - 1; + frames /= runtime->period_size; + frames *= runtime->period_size; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_FORWARD, &frames); + if (err < 0) + break; + } + return err; +} + +snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->state == SNDRV_PCM_STATE_XRUN || + runtime->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: write: recovering from %s\n", + runtime->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + mutex_unlock(&runtime->oss.params_lock); + ret = __snd_pcm_lib_xfer(substream, (void *)ptr, true, + frames, in_kernel); + mutex_lock(&runtime->oss.params_lock); + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + /* test, if we can't store new data, because the stream */ + /* has not been started */ + if (runtime->state == SNDRV_PCM_STATE_PREPARED) + return -EAGAIN; + } + return ret; +} + +snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *ptr, snd_pcm_uframes_t frames, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t delay; + int ret; + while (1) { + if (runtime->state == SNDRV_PCM_STATE_XRUN || + runtime->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: read: recovering from %s\n", + runtime->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + if (ret < 0) + break; + } else if (runtime->state == SNDRV_PCM_STATE_SETUP) { + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = snd_pcm_oss_capture_position_fixup(substream, &delay); + if (ret < 0) + break; + mutex_unlock(&runtime->oss.params_lock); + ret = __snd_pcm_lib_xfer(substream, (void *)ptr, true, + frames, in_kernel); + mutex_lock(&runtime->oss.params_lock); + if (ret == -EPIPE) { + if (runtime->state == SNDRV_PCM_STATE_DRAINING) { + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + if (ret < 0) + break; + } + continue; + } + if (ret != -ESTRPIPE) + break; + } + return ret; +} + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS +snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->state == SNDRV_PCM_STATE_XRUN || + runtime->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: writev: recovering from %s\n", + runtime->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = snd_pcm_kernel_writev(substream, bufs, frames); + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + + /* test, if we can't store new data, because the stream */ + /* has not been started */ + if (runtime->state == SNDRV_PCM_STATE_PREPARED) + return -EAGAIN; + } + return ret; +} + +snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + while (1) { + if (runtime->state == SNDRV_PCM_STATE_XRUN || + runtime->state == SNDRV_PCM_STATE_SUSPENDED) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: readv: recovering from %s\n", + runtime->state == SNDRV_PCM_STATE_XRUN ? + "XRUN" : "SUSPEND"); +#endif + ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + if (ret < 0) + break; + } else if (runtime->state == SNDRV_PCM_STATE_SETUP) { + ret = snd_pcm_oss_prepare(substream); + if (ret < 0) + break; + } + ret = snd_pcm_kernel_readv(substream, bufs, frames); + if (ret != -EPIPE && ret != -ESTRPIPE) + break; + } + return ret; +} +#endif /* CONFIG_SND_PCM_OSS_PLUGINS */ + +static ssize_t snd_pcm_oss_write2(struct snd_pcm_substream *substream, const char *buf, size_t bytes, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t frames, frames1; +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + if (runtime->oss.plugin_first) { + struct snd_pcm_plugin_channel *channels; + size_t oss_frame_bytes = (runtime->oss.plugin_first->src_width * runtime->oss.plugin_first->src_format.channels) / 8; + if (!in_kernel) { + if (copy_from_user(runtime->oss.buffer, (const char __force __user *)buf, bytes)) + return -EFAULT; + buf = runtime->oss.buffer; + } + frames = bytes / oss_frame_bytes; + frames1 = snd_pcm_plug_client_channels_buf(substream, (char *)buf, frames, &channels); + if (frames1 < 0) + return frames1; + frames1 = snd_pcm_plug_write_transfer(substream, channels, frames1); + if (frames1 <= 0) + return frames1; + bytes = frames1 * oss_frame_bytes; + } else +#endif + { + frames = bytes_to_frames(runtime, bytes); + frames1 = snd_pcm_oss_write3(substream, buf, frames, in_kernel); + if (frames1 <= 0) + return frames1; + bytes = frames_to_bytes(runtime, frames1); + } + return bytes; +} + +static ssize_t snd_pcm_oss_write1(struct snd_pcm_substream *substream, const char __user *buf, size_t bytes) +{ + size_t xfer = 0; + ssize_t tmp = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (atomic_read(&substream->mmap_count)) + return -ENXIO; + + atomic_inc(&runtime->oss.rw_ref); + while (bytes > 0) { + if (mutex_lock_interruptible(&runtime->oss.params_lock)) { + tmp = -ERESTARTSYS; + break; + } + tmp = snd_pcm_oss_make_ready_locked(substream); + if (tmp < 0) + goto err; + if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) { + tmp = bytes; + if (tmp + runtime->oss.buffer_used > runtime->oss.period_bytes) + tmp = runtime->oss.period_bytes - runtime->oss.buffer_used; + if (tmp > 0) { + if (copy_from_user(runtime->oss.buffer + runtime->oss.buffer_used, buf, tmp)) { + tmp = -EFAULT; + goto err; + } + } + runtime->oss.buffer_used += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + if (substream->oss.setup.partialfrag || + runtime->oss.buffer_used == runtime->oss.period_bytes) { + tmp = snd_pcm_oss_write2(substream, runtime->oss.buffer + runtime->oss.period_ptr, + runtime->oss.buffer_used - runtime->oss.period_ptr, 1); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + runtime->oss.period_ptr += tmp; + runtime->oss.period_ptr %= runtime->oss.period_bytes; + if (runtime->oss.period_ptr == 0 || + runtime->oss.period_ptr == runtime->oss.buffer_used) + runtime->oss.buffer_used = 0; + else if ((substream->f_flags & O_NONBLOCK) != 0) { + tmp = -EAGAIN; + goto err; + } + } + } else { + tmp = snd_pcm_oss_write2(substream, + (const char __force *)buf, + runtime->oss.period_bytes, 0); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + if ((substream->f_flags & O_NONBLOCK) != 0 && + tmp != runtime->oss.period_bytes) + tmp = -EAGAIN; + } + err: + mutex_unlock(&runtime->oss.params_lock); + if (tmp < 0) + break; + if (signal_pending(current)) { + tmp = -ERESTARTSYS; + break; + } + tmp = 0; + } + atomic_dec(&runtime->oss.rw_ref); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; +} + +static ssize_t snd_pcm_oss_read2(struct snd_pcm_substream *substream, char *buf, size_t bytes, int in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t frames, frames1; +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + char __user *final_dst = (char __force __user *)buf; + if (runtime->oss.plugin_first) { + struct snd_pcm_plugin_channel *channels; + size_t oss_frame_bytes = (runtime->oss.plugin_last->dst_width * runtime->oss.plugin_last->dst_format.channels) / 8; + if (!in_kernel) + buf = runtime->oss.buffer; + frames = bytes / oss_frame_bytes; + frames1 = snd_pcm_plug_client_channels_buf(substream, buf, frames, &channels); + if (frames1 < 0) + return frames1; + frames1 = snd_pcm_plug_read_transfer(substream, channels, frames1); + if (frames1 <= 0) + return frames1; + bytes = frames1 * oss_frame_bytes; + if (!in_kernel && copy_to_user(final_dst, buf, bytes)) + return -EFAULT; + } else +#endif + { + frames = bytes_to_frames(runtime, bytes); + frames1 = snd_pcm_oss_read3(substream, buf, frames, in_kernel); + if (frames1 <= 0) + return frames1; + bytes = frames_to_bytes(runtime, frames1); + } + return bytes; +} + +static ssize_t snd_pcm_oss_read1(struct snd_pcm_substream *substream, char __user *buf, size_t bytes) +{ + size_t xfer = 0; + ssize_t tmp = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (atomic_read(&substream->mmap_count)) + return -ENXIO; + + atomic_inc(&runtime->oss.rw_ref); + while (bytes > 0) { + if (mutex_lock_interruptible(&runtime->oss.params_lock)) { + tmp = -ERESTARTSYS; + break; + } + tmp = snd_pcm_oss_make_ready_locked(substream); + if (tmp < 0) + goto err; + if (bytes < runtime->oss.period_bytes || runtime->oss.buffer_used > 0) { + if (runtime->oss.buffer_used == 0) { + tmp = snd_pcm_oss_read2(substream, runtime->oss.buffer, runtime->oss.period_bytes, 1); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + runtime->oss.period_ptr = tmp; + runtime->oss.buffer_used = tmp; + } + tmp = bytes; + if ((size_t) tmp > runtime->oss.buffer_used) + tmp = runtime->oss.buffer_used; + if (copy_to_user(buf, runtime->oss.buffer + (runtime->oss.period_ptr - runtime->oss.buffer_used), tmp)) { + tmp = -EFAULT; + goto err; + } + buf += tmp; + bytes -= tmp; + xfer += tmp; + runtime->oss.buffer_used -= tmp; + } else { + tmp = snd_pcm_oss_read2(substream, (char __force *)buf, + runtime->oss.period_bytes, 0); + if (tmp <= 0) + goto err; + runtime->oss.bytes += tmp; + buf += tmp; + bytes -= tmp; + xfer += tmp; + } + err: + mutex_unlock(&runtime->oss.params_lock); + if (tmp < 0) + break; + if (signal_pending(current)) { + tmp = -ERESTARTSYS; + break; + } + tmp = 0; + } + atomic_dec(&runtime->oss.rw_ref); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : tmp; +} + +static int snd_pcm_oss_reset(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + int i; + + for (i = 0; i < 2; i++) { + substream = pcm_oss_file->streams[i]; + if (!substream) + continue; + runtime = substream->runtime; + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + mutex_lock(&runtime->oss.params_lock); + runtime->oss.prepare = 1; + runtime->oss.buffer_used = 0; + runtime->oss.prev_hw_ptr_period = 0; + runtime->oss.period_ptr = 0; + mutex_unlock(&runtime->oss.params_lock); + } + return 0; +} + +static int snd_pcm_oss_post(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream != NULL) { + err = snd_pcm_oss_make_ready(substream); + if (err < 0) + return err; + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_START, NULL); + } + /* note: all errors from the start action are ignored */ + /* OSS apps do not know, how to handle them */ + return 0; +} + +static int snd_pcm_oss_sync1(struct snd_pcm_substream *substream, size_t size) +{ + struct snd_pcm_runtime *runtime; + ssize_t result = 0; + snd_pcm_state_t state; + long res; + wait_queue_entry_t wait; + + runtime = substream->runtime; + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "sync1: size = %li\n", size); +#endif + while (1) { + result = snd_pcm_oss_write2(substream, runtime->oss.buffer, size, 1); + if (result > 0) { + runtime->oss.buffer_used = 0; + result = 0; + break; + } + if (result != 0 && result != -EAGAIN) + break; + result = 0; + set_current_state(TASK_INTERRUPTIBLE); + snd_pcm_stream_lock_irq(substream); + state = runtime->state; + snd_pcm_stream_unlock_irq(substream); + if (state != SNDRV_PCM_STATE_RUNNING) { + set_current_state(TASK_RUNNING); + break; + } + res = schedule_timeout(10 * HZ); + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + if (res == 0) { + pcm_err(substream->pcm, + "OSS sync error - DMA timeout\n"); + result = -EIO; + break; + } + } + remove_wait_queue(&runtime->sleep, &wait); + return result; +} + +static int snd_pcm_oss_sync(struct snd_pcm_oss_file *pcm_oss_file) +{ + int err = 0; + unsigned int saved_f_flags; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_format_t format; + unsigned long width; + size_t size; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream != NULL) { + runtime = substream->runtime; + if (atomic_read(&substream->mmap_count)) + goto __direct; + atomic_inc(&runtime->oss.rw_ref); + if (mutex_lock_interruptible(&runtime->oss.params_lock)) { + atomic_dec(&runtime->oss.rw_ref); + return -ERESTARTSYS; + } + err = snd_pcm_oss_make_ready_locked(substream); + if (err < 0) + goto unlock; + format = snd_pcm_oss_format_from(runtime->oss.format); + width = snd_pcm_format_physical_width(format); + if (runtime->oss.buffer_used > 0) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "sync: buffer_used\n"); +#endif + size = (8 * (runtime->oss.period_bytes - runtime->oss.buffer_used) + 7) / width; + snd_pcm_format_set_silence(format, + runtime->oss.buffer + runtime->oss.buffer_used, + size); + err = snd_pcm_oss_sync1(substream, runtime->oss.period_bytes); + if (err < 0) + goto unlock; + } else if (runtime->oss.period_ptr > 0) { +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "sync: period_ptr\n"); +#endif + size = runtime->oss.period_bytes - runtime->oss.period_ptr; + snd_pcm_format_set_silence(format, + runtime->oss.buffer, + size * 8 / width); + err = snd_pcm_oss_sync1(substream, size); + if (err < 0) + goto unlock; + } + /* + * The ALSA's period might be a bit large than OSS one. + * Fill the remain portion of ALSA period with zeros. + */ + size = runtime->control->appl_ptr % runtime->period_size; + if (size > 0) { + size = runtime->period_size - size; + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) + snd_pcm_lib_write(substream, NULL, size); + else if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + snd_pcm_lib_writev(substream, NULL, size); + } +unlock: + mutex_unlock(&runtime->oss.params_lock); + atomic_dec(&runtime->oss.rw_ref); + if (err < 0) + return err; + /* + * finish sync: drain the buffer + */ + __direct: + saved_f_flags = substream->f_flags; + substream->f_flags &= ~O_NONBLOCK; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); + substream->f_flags = saved_f_flags; + if (err < 0) + return err; + mutex_lock(&runtime->oss.params_lock); + runtime->oss.prepare = 1; + mutex_unlock(&runtime->oss.params_lock); + } + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream != NULL) { + err = snd_pcm_oss_make_ready(substream); + if (err < 0) + return err; + runtime = substream->runtime; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + if (err < 0) + return err; + mutex_lock(&runtime->oss.params_lock); + runtime->oss.buffer_used = 0; + runtime->oss.prepare = 1; + mutex_unlock(&runtime->oss.params_lock); + } + return 0; +} + +static int snd_pcm_oss_set_rate(struct snd_pcm_oss_file *pcm_oss_file, int rate) +{ + int idx; + + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + int err; + + if (substream == NULL) + continue; + runtime = substream->runtime; + if (rate < 1000) + rate = 1000; + else if (rate > 192000) + rate = 192000; + err = lock_params(runtime); + if (err < 0) + return err; + if (runtime->oss.rate != rate) { + runtime->oss.params = 1; + runtime->oss.rate = rate; + } + unlock_params(runtime); + } + return snd_pcm_oss_get_rate(pcm_oss_file); +} + +static int snd_pcm_oss_get_rate(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream); + if (err < 0) + return err; + return substream->runtime->oss.rate; +} + +static int snd_pcm_oss_set_channels(struct snd_pcm_oss_file *pcm_oss_file, unsigned int channels) +{ + int idx; + if (channels < 1) + channels = 1; + if (channels > 128) + return -EINVAL; + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + int err; + + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + if (runtime->oss.channels != channels) { + runtime->oss.params = 1; + runtime->oss.channels = channels; + } + unlock_params(runtime); + } + return snd_pcm_oss_get_channels(pcm_oss_file); +} + +static int snd_pcm_oss_get_channels(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream); + if (err < 0) + return err; + return substream->runtime->oss.channels; +} + +static int snd_pcm_oss_get_block_size(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream); + if (err < 0) + return err; + return substream->runtime->oss.period_bytes; +} + +static int snd_pcm_oss_get_formats(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + int direct; + struct snd_pcm_hw_params *params; + unsigned int formats = 0; + const struct snd_mask *format_mask; + int fmt; + + err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream); + if (err < 0) + return err; + if (atomic_read(&substream->mmap_count)) + direct = 1; + else + direct = substream->oss.setup.direct; + if (!direct) + return AFMT_MU_LAW | AFMT_U8 | + AFMT_S16_LE | AFMT_S16_BE | + AFMT_S8 | AFMT_U16_LE | + AFMT_U16_BE | + AFMT_S32_LE | AFMT_S32_BE | + AFMT_S24_LE | AFMT_S24_BE | + AFMT_S24_PACKED; + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + _snd_pcm_hw_params_any(params); + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto error; + format_mask = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + for (fmt = 0; fmt < 32; ++fmt) { + if (snd_mask_test(format_mask, fmt)) { + int f = snd_pcm_oss_format_to((__force snd_pcm_format_t)fmt); + if (f >= 0) + formats |= f; + } + } + + error: + kfree(params); + return err < 0 ? err : formats; +} + +static int snd_pcm_oss_set_format(struct snd_pcm_oss_file *pcm_oss_file, int format) +{ + int formats, idx; + int err; + + if (format != AFMT_QUERY) { + formats = snd_pcm_oss_get_formats(pcm_oss_file); + if (formats < 0) + return formats; + if (!(formats & format)) + format = AFMT_U8; + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + if (runtime->oss.format != format) { + runtime->oss.params = 1; + runtime->oss.format = format; + } + unlock_params(runtime); + } + } + return snd_pcm_oss_get_format(pcm_oss_file); +} + +static int snd_pcm_oss_get_format(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_oss_get_active_substream(pcm_oss_file, &substream); + if (err < 0) + return err; + return substream->runtime->oss.format; +} + +static int snd_pcm_oss_set_subdivide1(struct snd_pcm_substream *substream, int subdivide) +{ + struct snd_pcm_runtime *runtime; + + runtime = substream->runtime; + if (subdivide == 0) { + subdivide = runtime->oss.subdivision; + if (subdivide == 0) + subdivide = 1; + return subdivide; + } + if (runtime->oss.subdivision || runtime->oss.fragshift) + return -EINVAL; + if (subdivide != 1 && subdivide != 2 && subdivide != 4 && + subdivide != 8 && subdivide != 16) + return -EINVAL; + runtime->oss.subdivision = subdivide; + runtime->oss.params = 1; + return subdivide; +} + +static int snd_pcm_oss_set_subdivide(struct snd_pcm_oss_file *pcm_oss_file, int subdivide) +{ + int err = -EINVAL, idx; + + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + err = snd_pcm_oss_set_subdivide1(substream, subdivide); + unlock_params(runtime); + if (err < 0) + return err; + } + return err; +} + +static int snd_pcm_oss_set_fragment1(struct snd_pcm_substream *substream, unsigned int val) +{ + struct snd_pcm_runtime *runtime; + int fragshift; + + runtime = substream->runtime; + if (runtime->oss.subdivision || runtime->oss.fragshift) + return -EINVAL; + fragshift = val & 0xffff; + if (fragshift >= 25) /* should be large enough */ + return -EINVAL; + runtime->oss.fragshift = fragshift; + runtime->oss.maxfrags = (val >> 16) & 0xffff; + if (runtime->oss.fragshift < 4) /* < 16 */ + runtime->oss.fragshift = 4; + if (runtime->oss.maxfrags < 2) + runtime->oss.maxfrags = 2; + runtime->oss.params = 1; + return 0; +} + +static int snd_pcm_oss_set_fragment(struct snd_pcm_oss_file *pcm_oss_file, unsigned int val) +{ + int err = -EINVAL, idx; + + for (idx = 1; idx >= 0; --idx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + continue; + runtime = substream->runtime; + err = lock_params(runtime); + if (err < 0) + return err; + err = snd_pcm_oss_set_fragment1(substream, val); + unlock_params(runtime); + if (err < 0) + return err; + } + return err; +} + +static int snd_pcm_oss_nonblock(struct file * file) +{ + spin_lock(&file->f_lock); + file->f_flags |= O_NONBLOCK; + spin_unlock(&file->f_lock); + return 0; +} + +static int snd_pcm_oss_get_caps1(struct snd_pcm_substream *substream, int res) +{ + + if (substream == NULL) { + res &= ~DSP_CAP_DUPLEX; + return res; + } +#ifdef DSP_CAP_MULTI + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + if (substream->pstr->substream_count > 1) + res |= DSP_CAP_MULTI; +#endif + /* DSP_CAP_REALTIME is set all times: */ + /* all ALSA drivers can return actual pointer in ring buffer */ +#if defined(DSP_CAP_REALTIME) && 0 + { + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->info & (SNDRV_PCM_INFO_BLOCK_TRANSFER|SNDRV_PCM_INFO_BATCH)) + res &= ~DSP_CAP_REALTIME; + } +#endif + return res; +} + +static int snd_pcm_oss_get_caps(struct snd_pcm_oss_file *pcm_oss_file) +{ + int result, idx; + + result = DSP_CAP_TRIGGER | DSP_CAP_MMAP | DSP_CAP_DUPLEX | DSP_CAP_REALTIME; + for (idx = 0; idx < 2; idx++) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[idx]; + result = snd_pcm_oss_get_caps1(substream, result); + } + result |= 0x0001; /* revision - same as SB AWE 64 */ + return result; +} + +static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream, + snd_pcm_uframes_t hw_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr; + appl_ptr = hw_ptr + runtime->buffer_size; + appl_ptr %= runtime->boundary; + runtime->control->appl_ptr = appl_ptr; +} + +static int snd_pcm_oss_set_trigger(struct snd_pcm_oss_file *pcm_oss_file, int trigger) +{ + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL; + int err, cmd; + +#ifdef OSS_DEBUG + pr_debug("pcm_oss: trigger = 0x%x\n", trigger); +#endif + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (psubstream) { + err = snd_pcm_oss_make_ready(psubstream); + if (err < 0) + return err; + } + if (csubstream) { + err = snd_pcm_oss_make_ready(csubstream); + if (err < 0) + return err; + } + if (psubstream) { + runtime = psubstream->runtime; + cmd = 0; + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + if (trigger & PCM_ENABLE_OUTPUT) { + if (runtime->oss.trigger) + goto _skip1; + if (atomic_read(&psubstream->mmap_count)) + snd_pcm_oss_simulate_fill(psubstream, + get_hw_ptr_period(runtime)); + runtime->oss.trigger = 1; + runtime->start_threshold = 1; + cmd = SNDRV_PCM_IOCTL_START; + } else { + if (!runtime->oss.trigger) + goto _skip1; + runtime->oss.trigger = 0; + runtime->start_threshold = runtime->boundary; + cmd = SNDRV_PCM_IOCTL_DROP; + runtime->oss.prepare = 1; + } + _skip1: + mutex_unlock(&runtime->oss.params_lock); + if (cmd) { + err = snd_pcm_kernel_ioctl(psubstream, cmd, NULL); + if (err < 0) + return err; + } + } + if (csubstream) { + runtime = csubstream->runtime; + cmd = 0; + if (mutex_lock_interruptible(&runtime->oss.params_lock)) + return -ERESTARTSYS; + if (trigger & PCM_ENABLE_INPUT) { + if (runtime->oss.trigger) + goto _skip2; + runtime->oss.trigger = 1; + runtime->start_threshold = 1; + cmd = SNDRV_PCM_IOCTL_START; + } else { + if (!runtime->oss.trigger) + goto _skip2; + runtime->oss.trigger = 0; + runtime->start_threshold = runtime->boundary; + cmd = SNDRV_PCM_IOCTL_DROP; + runtime->oss.prepare = 1; + } + _skip2: + mutex_unlock(&runtime->oss.params_lock); + if (cmd) { + err = snd_pcm_kernel_ioctl(csubstream, cmd, NULL); + if (err < 0) + return err; + } + } + return 0; +} + +static int snd_pcm_oss_get_trigger(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL; + int result = 0; + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (psubstream && psubstream->runtime && psubstream->runtime->oss.trigger) + result |= PCM_ENABLE_OUTPUT; + if (csubstream && csubstream->runtime && csubstream->runtime->oss.trigger) + result |= PCM_ENABLE_INPUT; + return result; +} + +static int snd_pcm_oss_get_odelay(struct snd_pcm_oss_file *pcm_oss_file) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t delay; + int err; + + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + return -EINVAL; + err = snd_pcm_oss_make_ready(substream); + if (err < 0) + return err; + runtime = substream->runtime; + if (runtime->oss.params || runtime->oss.prepare) + return 0; + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); + if (err == -EPIPE) + delay = 0; /* hack for broken OSS applications */ + else if (err < 0) + return err; + return snd_pcm_oss_bytes(substream, delay); +} + +static int snd_pcm_oss_get_ptr(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct count_info __user * _info) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t delay; + int fixup; + struct count_info info; + int err; + + if (_info == NULL) + return -EFAULT; + substream = pcm_oss_file->streams[stream]; + if (substream == NULL) + return -EINVAL; + err = snd_pcm_oss_make_ready(substream); + if (err < 0) + return err; + runtime = substream->runtime; + if (runtime->oss.params || runtime->oss.prepare) { + memset(&info, 0, sizeof(info)); + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; + } + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &delay); + if (err == -EPIPE || err == -ESTRPIPE || (! err && delay < 0)) { + err = 0; + delay = 0; + fixup = 0; + } else { + fixup = runtime->oss.buffer_used; + } + } else { + err = snd_pcm_oss_capture_position_fixup(substream, &delay); + fixup = -runtime->oss.buffer_used; + } + if (err < 0) + return err; + info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size); + if (atomic_read(&substream->mmap_count)) { + snd_pcm_sframes_t n; + delay = get_hw_ptr_period(runtime); + n = delay - runtime->oss.prev_hw_ptr_period; + if (n < 0) + n += runtime->boundary; + info.blocks = n / runtime->period_size; + runtime->oss.prev_hw_ptr_period = delay; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_oss_simulate_fill(substream, delay); + info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX; + } else { + delay = snd_pcm_oss_bytes(substream, delay); + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (substream->oss.setup.buggyptr) + info.blocks = (runtime->oss.buffer_bytes - delay - fixup) / runtime->oss.period_bytes; + else + info.blocks = (delay + fixup) / runtime->oss.period_bytes; + info.bytes = (runtime->oss.bytes - delay) & INT_MAX; + } else { + delay += fixup; + info.blocks = delay / runtime->oss.period_bytes; + info.bytes = (runtime->oss.bytes + delay) & INT_MAX; + } + } + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_pcm_oss_get_space(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct audio_buf_info __user *_info) +{ + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t avail; + int fixup; + struct audio_buf_info info; + int err; + + if (_info == NULL) + return -EFAULT; + substream = pcm_oss_file->streams[stream]; + if (substream == NULL) + return -EINVAL; + runtime = substream->runtime; + + if (runtime->oss.params) { + err = snd_pcm_oss_change_params(substream, false); + if (err < 0) + return err; + } + + info.fragsize = runtime->oss.period_bytes; + info.fragstotal = runtime->periods; + if (runtime->oss.prepare) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + info.bytes = runtime->oss.period_bytes * runtime->oss.periods; + info.fragments = runtime->oss.periods; + } else { + info.bytes = 0; + info.fragments = 0; + } + } else { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + err = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DELAY, &avail); + if (err == -EPIPE || err == -ESTRPIPE || (! err && avail < 0)) { + avail = runtime->buffer_size; + err = 0; + fixup = 0; + } else { + avail = runtime->buffer_size - avail; + fixup = -runtime->oss.buffer_used; + } + } else { + err = snd_pcm_oss_capture_position_fixup(substream, &avail); + fixup = runtime->oss.buffer_used; + } + if (err < 0) + return err; + info.bytes = snd_pcm_oss_bytes(substream, avail) + fixup; + info.fragments = info.bytes / runtime->oss.period_bytes; + } + +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, + "pcm_oss: space: bytes = %i, fragments = %i, fragstotal = %i, fragsize = %i\n", + info.bytes, info.fragments, info.fragstotal, info.fragsize); +#endif + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_pcm_oss_get_mapbuf(struct snd_pcm_oss_file *pcm_oss_file, int stream, struct buffmem_desc __user * _info) +{ + // it won't be probably implemented + // pr_debug("TODO: snd_pcm_oss_get_mapbuf\n"); + return -EINVAL; +} + +static const char *strip_task_path(const char *path) +{ + const char *ptr, *ptrl = NULL; + for (ptr = path; *ptr; ptr++) { + if (*ptr == '/') + ptrl = ptr + 1; + } + return ptrl; +} + +static void snd_pcm_oss_look_for_setup(struct snd_pcm *pcm, int stream, + const char *task_name, + struct snd_pcm_oss_setup *rsetup) +{ + struct snd_pcm_oss_setup *setup; + + mutex_lock(&pcm->streams[stream].oss.setup_mutex); + do { + for (setup = pcm->streams[stream].oss.setup_list; setup; + setup = setup->next) { + if (!strcmp(setup->task_name, task_name)) + goto out; + } + } while ((task_name = strip_task_path(task_name)) != NULL); + out: + if (setup) + *rsetup = *setup; + mutex_unlock(&pcm->streams[stream].oss.setup_mutex); +} + +static void snd_pcm_oss_release_substream(struct snd_pcm_substream *substream) +{ + snd_pcm_oss_release_buffers(substream); + substream->oss.oss = 0; +} + +static void snd_pcm_oss_init_substream(struct snd_pcm_substream *substream, + struct snd_pcm_oss_setup *setup, + int minor) +{ + struct snd_pcm_runtime *runtime; + + substream->oss.oss = 1; + substream->oss.setup = *setup; + if (setup->nonblock) + substream->f_flags |= O_NONBLOCK; + else if (setup->block) + substream->f_flags &= ~O_NONBLOCK; + runtime = substream->runtime; + runtime->oss.params = 1; + runtime->oss.trigger = 1; + runtime->oss.rate = 8000; + mutex_init(&runtime->oss.params_lock); + switch (SNDRV_MINOR_OSS_DEVICE(minor)) { + case SNDRV_MINOR_OSS_PCM_8: + runtime->oss.format = AFMT_U8; + break; + case SNDRV_MINOR_OSS_PCM_16: + runtime->oss.format = AFMT_S16_LE; + break; + default: + runtime->oss.format = AFMT_MU_LAW; + } + runtime->oss.channels = 1; + runtime->oss.fragshift = 0; + runtime->oss.maxfrags = 0; + runtime->oss.subdivision = 0; + substream->pcm_release = snd_pcm_oss_release_substream; + atomic_set(&runtime->oss.rw_ref, 0); +} + +static int snd_pcm_oss_release_file(struct snd_pcm_oss_file *pcm_oss_file) +{ + int cidx; + if (!pcm_oss_file) + return 0; + for (cidx = 0; cidx < 2; ++cidx) { + struct snd_pcm_substream *substream = pcm_oss_file->streams[cidx]; + if (substream) + snd_pcm_release_substream(substream); + } + kfree(pcm_oss_file); + return 0; +} + +static int snd_pcm_oss_open_file(struct file *file, + struct snd_pcm *pcm, + struct snd_pcm_oss_file **rpcm_oss_file, + int minor, + struct snd_pcm_oss_setup *setup) +{ + int idx, err; + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream; + fmode_t f_mode = file->f_mode; + + if (rpcm_oss_file) + *rpcm_oss_file = NULL; + + pcm_oss_file = kzalloc(sizeof(*pcm_oss_file), GFP_KERNEL); + if (pcm_oss_file == NULL) + return -ENOMEM; + + if ((f_mode & (FMODE_WRITE|FMODE_READ)) == (FMODE_WRITE|FMODE_READ) && + (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)) + f_mode = FMODE_WRITE; + + file->f_flags &= ~O_APPEND; + for (idx = 0; idx < 2; idx++) { + if (setup[idx].disable) + continue; + if (! pcm->streams[idx].substream_count) + continue; /* no matching substream */ + if (idx == SNDRV_PCM_STREAM_PLAYBACK) { + if (! (f_mode & FMODE_WRITE)) + continue; + } else { + if (! (f_mode & FMODE_READ)) + continue; + } + err = snd_pcm_open_substream(pcm, idx, file, &substream); + if (err < 0) { + snd_pcm_oss_release_file(pcm_oss_file); + return err; + } + + pcm_oss_file->streams[idx] = substream; + snd_pcm_oss_init_substream(substream, &setup[idx], minor); + } + + if (!pcm_oss_file->streams[0] && !pcm_oss_file->streams[1]) { + snd_pcm_oss_release_file(pcm_oss_file); + return -EINVAL; + } + + file->private_data = pcm_oss_file; + if (rpcm_oss_file) + *rpcm_oss_file = pcm_oss_file; + return 0; +} + + +static int snd_task_name(struct task_struct *task, char *name, size_t size) +{ + unsigned int idx; + + if (snd_BUG_ON(!task || !name || size < 2)) + return -EINVAL; + for (idx = 0; idx < sizeof(task->comm) && idx + 1 < size; idx++) + name[idx] = task->comm[idx]; + name[idx] = '\0'; + return 0; +} + +static int snd_pcm_oss_open(struct inode *inode, struct file *file) +{ + int err; + char task_name[32]; + struct snd_pcm *pcm; + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_oss_setup setup[2]; + int nonblock; + wait_queue_entry_t wait; + + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + pcm = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM); + if (pcm == NULL) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(pcm->card, file); + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { + err = -EFAULT; + goto __error2; + } + if (snd_task_name(current, task_name, sizeof(task_name)) < 0) { + err = -EFAULT; + goto __error; + } + memset(setup, 0, sizeof(setup)); + if (file->f_mode & FMODE_WRITE) + snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_PLAYBACK, + task_name, &setup[0]); + if (file->f_mode & FMODE_READ) + snd_pcm_oss_look_for_setup(pcm, SNDRV_PCM_STREAM_CAPTURE, + task_name, &setup[1]); + + nonblock = !!(file->f_flags & O_NONBLOCK); + if (!nonblock) + nonblock = nonblock_open; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&pcm->open_wait, &wait); + mutex_lock(&pcm->open_mutex); + while (1) { + err = snd_pcm_oss_open_file(file, pcm, &pcm_oss_file, + iminor(inode), setup); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (nonblock) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&pcm->open_mutex); + schedule(); + mutex_lock(&pcm->open_mutex); + if (pcm->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&pcm->open_wait, &wait); + mutex_unlock(&pcm->open_mutex); + if (err < 0) + goto __error; + snd_card_unref(pcm->card); + return err; + + __error: + module_put(pcm->card->module); + __error2: + snd_card_file_remove(pcm->card, file); + __error1: + if (pcm) + snd_card_unref(pcm->card); + return err; +} + +static int snd_pcm_oss_release(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + struct snd_pcm_oss_file *pcm_oss_file; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (snd_BUG_ON(!substream)) + return -ENXIO; + pcm = substream->pcm; + if (!pcm->card->shutdown) + snd_pcm_oss_sync(pcm_oss_file); + mutex_lock(&pcm->open_mutex); + snd_pcm_oss_release_file(pcm_oss_file); + mutex_unlock(&pcm->open_mutex); + wake_up(&pcm->open_wait); + module_put(pcm->card->module); + snd_card_file_remove(pcm->card, file); + return 0; +} + +static long snd_pcm_oss_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_pcm_oss_file *pcm_oss_file; + int __user *p = (int __user *)arg; + int res; + + pcm_oss_file = file->private_data; + if (cmd == OSS_GETVERSION) + return put_user(SNDRV_OSS_VERSION, p); + if (cmd == OSS_ALSAEMULVER) + return put_user(1, p); +#if IS_REACHABLE(CONFIG_SND_MIXER_OSS) + if (((cmd >> 8) & 0xff) == 'M') { /* mixer ioctl - for OSS compatibility */ + struct snd_pcm_substream *substream; + int idx; + for (idx = 0; idx < 2; ++idx) { + substream = pcm_oss_file->streams[idx]; + if (substream != NULL) + break; + } + if (snd_BUG_ON(idx >= 2)) + return -ENXIO; + return snd_mixer_oss_ioctl_card(substream->pcm->card, cmd, arg); + } +#endif + if (((cmd >> 8) & 0xff) != 'P') + return -EINVAL; +#ifdef OSS_DEBUG + pr_debug("pcm_oss: ioctl = 0x%x\n", cmd); +#endif + switch (cmd) { + case SNDCTL_DSP_RESET: + return snd_pcm_oss_reset(pcm_oss_file); + case SNDCTL_DSP_SYNC: + return snd_pcm_oss_sync(pcm_oss_file); + case SNDCTL_DSP_SPEED: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_rate(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_RATE: + res = snd_pcm_oss_get_rate(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_STEREO: + if (get_user(res, p)) + return -EFAULT; + res = res > 0 ? 2 : 1; + res = snd_pcm_oss_set_channels(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(--res, p); + case SNDCTL_DSP_GETBLKSIZE: + res = snd_pcm_oss_get_block_size(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETFMT: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_format(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_BITS: + res = snd_pcm_oss_get_format(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_CHANNELS: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_channels(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_READ_CHANNELS: + res = snd_pcm_oss_get_channels(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + return -EIO; + case SNDCTL_DSP_POST: + return snd_pcm_oss_post(pcm_oss_file); + case SNDCTL_DSP_SUBDIVIDE: + if (get_user(res, p)) + return -EFAULT; + res = snd_pcm_oss_set_subdivide(pcm_oss_file, res); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETFRAGMENT: + if (get_user(res, p)) + return -EFAULT; + return snd_pcm_oss_set_fragment(pcm_oss_file, res); + case SNDCTL_DSP_GETFMTS: + res = snd_pcm_oss_get_formats(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: + return snd_pcm_oss_get_space(pcm_oss_file, + cmd == SNDCTL_DSP_GETISPACE ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct audio_buf_info __user *) arg); + case SNDCTL_DSP_NONBLOCK: + return snd_pcm_oss_nonblock(file); + case SNDCTL_DSP_GETCAPS: + res = snd_pcm_oss_get_caps(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_GETTRIGGER: + res = snd_pcm_oss_get_trigger(pcm_oss_file); + if (res < 0) + return res; + return put_user(res, p); + case SNDCTL_DSP_SETTRIGGER: + if (get_user(res, p)) + return -EFAULT; + return snd_pcm_oss_set_trigger(pcm_oss_file, res); + case SNDCTL_DSP_GETIPTR: + case SNDCTL_DSP_GETOPTR: + return snd_pcm_oss_get_ptr(pcm_oss_file, + cmd == SNDCTL_DSP_GETIPTR ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct count_info __user *) arg); + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + return snd_pcm_oss_get_mapbuf(pcm_oss_file, + cmd == SNDCTL_DSP_MAPINBUF ? + SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK, + (struct buffmem_desc __user *) arg); + case SNDCTL_DSP_SETSYNCRO: + /* stop DMA now.. */ + return 0; + case SNDCTL_DSP_SETDUPLEX: + if (snd_pcm_oss_get_caps(pcm_oss_file) & DSP_CAP_DUPLEX) + return 0; + return -EIO; + case SNDCTL_DSP_GETODELAY: + res = snd_pcm_oss_get_odelay(pcm_oss_file); + if (res < 0) { + /* it's for sure, some broken apps don't check for error codes */ + put_user(0, p); + return res; + } + return put_user(res, p); + case SNDCTL_DSP_PROFILE: + return 0; /* silently ignore */ + default: + pr_debug("pcm_oss: unknown command = 0x%x\n", cmd); + } + return -EINVAL; +} + +#ifdef CONFIG_COMPAT +/* all compatible */ +static long snd_pcm_oss_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + /* + * Everything is compatbile except SNDCTL_DSP_MAPINBUF/SNDCTL_DSP_MAPOUTBUF, + * which are not implemented for the native case either + */ + return snd_pcm_oss_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define snd_pcm_oss_ioctl_compat NULL +#endif + +static ssize_t snd_pcm_oss_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + if (substream == NULL) + return -ENXIO; + substream->f_flags = file->f_flags & O_NONBLOCK; +#ifndef OSS_DEBUG + return snd_pcm_oss_read1(substream, buf, count); +#else + { + ssize_t res = snd_pcm_oss_read1(substream, buf, count); + pcm_dbg(substream->pcm, + "pcm_oss: read %li bytes (returned %li bytes)\n", + (long)count, (long)res); + return res; + } +#endif +} + +static ssize_t snd_pcm_oss_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream; + long result; + + pcm_oss_file = file->private_data; + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream == NULL) + return -ENXIO; + substream->f_flags = file->f_flags & O_NONBLOCK; + result = snd_pcm_oss_write1(substream, buf, count); +#ifdef OSS_DEBUG + pcm_dbg(substream->pcm, "pcm_oss: write %li bytes (wrote %li bytes)\n", + (long)count, (long)result); +#endif + return result; +} + +static int snd_pcm_oss_playback_ready(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (atomic_read(&substream->mmap_count)) + return runtime->oss.prev_hw_ptr_period != + get_hw_ptr_period(runtime); + else + return snd_pcm_playback_avail(runtime) >= + runtime->oss.period_frames; +} + +static int snd_pcm_oss_capture_ready(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (atomic_read(&substream->mmap_count)) + return runtime->oss.prev_hw_ptr_period != + get_hw_ptr_period(runtime); + else + return snd_pcm_capture_avail(runtime) >= + runtime->oss.period_frames; +} + +static __poll_t snd_pcm_oss_poll(struct file *file, poll_table * wait) +{ + struct snd_pcm_oss_file *pcm_oss_file; + __poll_t mask; + struct snd_pcm_substream *psubstream = NULL, *csubstream = NULL; + + pcm_oss_file = file->private_data; + + psubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + csubstream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + + mask = 0; + if (psubstream != NULL) { + struct snd_pcm_runtime *runtime = psubstream->runtime; + poll_wait(file, &runtime->sleep, wait); + snd_pcm_stream_lock_irq(psubstream); + if (runtime->state != SNDRV_PCM_STATE_DRAINING && + (runtime->state != SNDRV_PCM_STATE_RUNNING || + snd_pcm_oss_playback_ready(psubstream))) + mask |= EPOLLOUT | EPOLLWRNORM; + snd_pcm_stream_unlock_irq(psubstream); + } + if (csubstream != NULL) { + struct snd_pcm_runtime *runtime = csubstream->runtime; + snd_pcm_state_t ostate; + poll_wait(file, &runtime->sleep, wait); + snd_pcm_stream_lock_irq(csubstream); + ostate = runtime->state; + if (ostate != SNDRV_PCM_STATE_RUNNING || + snd_pcm_oss_capture_ready(csubstream)) + mask |= EPOLLIN | EPOLLRDNORM; + snd_pcm_stream_unlock_irq(csubstream); + if (ostate != SNDRV_PCM_STATE_RUNNING && runtime->oss.trigger) { + struct snd_pcm_oss_file ofile; + memset(&ofile, 0, sizeof(ofile)); + ofile.streams[SNDRV_PCM_STREAM_CAPTURE] = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + runtime->oss.trigger = 0; + snd_pcm_oss_set_trigger(&ofile, PCM_ENABLE_INPUT); + } + } + + return mask; +} + +static int snd_pcm_oss_mmap(struct file *file, struct vm_area_struct *area) +{ + struct snd_pcm_oss_file *pcm_oss_file; + struct snd_pcm_substream *substream = NULL; + struct snd_pcm_runtime *runtime; + int err; + +#ifdef OSS_DEBUG + pr_debug("pcm_oss: mmap begin\n"); +#endif + pcm_oss_file = file->private_data; + switch ((area->vm_flags & (VM_READ | VM_WRITE))) { + case VM_READ | VM_WRITE: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + if (substream) + break; + fallthrough; + case VM_READ: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_CAPTURE]; + break; + case VM_WRITE: + substream = pcm_oss_file->streams[SNDRV_PCM_STREAM_PLAYBACK]; + break; + default: + return -EINVAL; + } + /* set VM_READ access as well to fix memset() routines that do + reads before writes (to improve performance) */ + vm_flags_set(area, VM_READ); + if (substream == NULL) + return -ENXIO; + runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP_VALID)) + return -EIO; + if (runtime->info & SNDRV_PCM_INFO_INTERLEAVED) + runtime->access = SNDRV_PCM_ACCESS_MMAP_INTERLEAVED; + else + return -EIO; + + if (runtime->oss.params) { + /* use mutex_trylock() for params_lock for avoiding a deadlock + * between mmap_lock and params_lock taken by + * copy_from/to_user() in snd_pcm_oss_write/read() + */ + err = snd_pcm_oss_change_params(substream, true); + if (err < 0) + return err; + } +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + if (runtime->oss.plugin_first != NULL) + return -EIO; +#endif + + if (area->vm_pgoff != 0) + return -EINVAL; + + err = snd_pcm_mmap_data(substream, file, area); + if (err < 0) + return err; + runtime->oss.mmap_bytes = area->vm_end - area->vm_start; + runtime->silence_threshold = 0; + runtime->silence_size = 0; +#ifdef OSS_DEBUG + pr_debug("pcm_oss: mmap ok, bytes = 0x%x\n", + runtime->oss.mmap_bytes); +#endif + /* In mmap mode we never stop */ + runtime->stop_threshold = runtime->boundary; + + return 0; +} + +#ifdef CONFIG_SND_VERBOSE_PROCFS +/* + * /proc interface + */ + +static void snd_pcm_oss_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + struct snd_pcm_oss_setup *setup = pstr->oss.setup_list; + mutex_lock(&pstr->oss.setup_mutex); + while (setup) { + snd_iprintf(buffer, "%s %u %u%s%s%s%s%s%s\n", + setup->task_name, + setup->periods, + setup->period_size, + setup->disable ? " disable" : "", + setup->direct ? " direct" : "", + setup->block ? " block" : "", + setup->nonblock ? " non-block" : "", + setup->partialfrag ? " partial-frag" : "", + setup->nosilence ? " no-silence" : ""); + setup = setup->next; + } + mutex_unlock(&pstr->oss.setup_mutex); +} + +static void snd_pcm_oss_proc_free_setup_list(struct snd_pcm_str * pstr) +{ + struct snd_pcm_oss_setup *setup, *setupn; + + for (setup = pstr->oss.setup_list, pstr->oss.setup_list = NULL; + setup; setup = setupn) { + setupn = setup->next; + kfree(setup->task_name); + kfree(setup); + } + pstr->oss.setup_list = NULL; +} + +static void snd_pcm_oss_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + char line[128], str[32], task_name[32]; + const char *ptr; + int idx1; + struct snd_pcm_oss_setup *setup, *setup1, template; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + mutex_lock(&pstr->oss.setup_mutex); + memset(&template, 0, sizeof(template)); + ptr = snd_info_get_str(task_name, line, sizeof(task_name)); + if (!strcmp(task_name, "clear") || !strcmp(task_name, "erase")) { + snd_pcm_oss_proc_free_setup_list(pstr); + mutex_unlock(&pstr->oss.setup_mutex); + continue; + } + for (setup = pstr->oss.setup_list; setup; setup = setup->next) { + if (!strcmp(setup->task_name, task_name)) { + template = *setup; + break; + } + } + ptr = snd_info_get_str(str, ptr, sizeof(str)); + template.periods = simple_strtoul(str, NULL, 10); + ptr = snd_info_get_str(str, ptr, sizeof(str)); + template.period_size = simple_strtoul(str, NULL, 10); + for (idx1 = 31; idx1 >= 0; idx1--) + if (template.period_size & (1 << idx1)) + break; + for (idx1--; idx1 >= 0; idx1--) + template.period_size &= ~(1 << idx1); + do { + ptr = snd_info_get_str(str, ptr, sizeof(str)); + if (!strcmp(str, "disable")) { + template.disable = 1; + } else if (!strcmp(str, "direct")) { + template.direct = 1; + } else if (!strcmp(str, "block")) { + template.block = 1; + } else if (!strcmp(str, "non-block")) { + template.nonblock = 1; + } else if (!strcmp(str, "partial-frag")) { + template.partialfrag = 1; + } else if (!strcmp(str, "no-silence")) { + template.nosilence = 1; + } else if (!strcmp(str, "buggy-ptr")) { + template.buggyptr = 1; + } + } while (*str); + if (setup == NULL) { + setup = kmalloc(sizeof(*setup), GFP_KERNEL); + if (! setup) { + buffer->error = -ENOMEM; + mutex_unlock(&pstr->oss.setup_mutex); + return; + } + if (pstr->oss.setup_list == NULL) + pstr->oss.setup_list = setup; + else { + for (setup1 = pstr->oss.setup_list; + setup1->next; setup1 = setup1->next); + setup1->next = setup; + } + template.task_name = kstrdup(task_name, GFP_KERNEL); + if (! template.task_name) { + kfree(setup); + buffer->error = -ENOMEM; + mutex_unlock(&pstr->oss.setup_mutex); + return; + } + } + *setup = template; + mutex_unlock(&pstr->oss.setup_mutex); + } +} + +static void snd_pcm_oss_proc_init(struct snd_pcm *pcm) +{ + int stream; + for (stream = 0; stream < 2; ++stream) { + struct snd_info_entry *entry; + struct snd_pcm_str *pstr = &pcm->streams[stream]; + if (pstr->substream_count == 0) + continue; + entry = snd_info_create_card_entry(pcm->card, "oss", pstr->proc_root); + if (entry) { + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->mode = S_IFREG | 0644; + entry->c.text.read = snd_pcm_oss_proc_read; + entry->c.text.write = snd_pcm_oss_proc_write; + entry->private_data = pstr; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + pstr->oss.proc_entry = entry; + } +} + +static void snd_pcm_oss_proc_done(struct snd_pcm *pcm) +{ + int stream; + for (stream = 0; stream < 2; ++stream) { + struct snd_pcm_str *pstr = &pcm->streams[stream]; + snd_info_free_entry(pstr->oss.proc_entry); + pstr->oss.proc_entry = NULL; + snd_pcm_oss_proc_free_setup_list(pstr); + } +} +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +static inline void snd_pcm_oss_proc_init(struct snd_pcm *pcm) +{ +} +static inline void snd_pcm_oss_proc_done(struct snd_pcm *pcm) +{ +} +#endif /* CONFIG_SND_VERBOSE_PROCFS */ + +/* + * ENTRY functions + */ + +static const struct file_operations snd_pcm_oss_f_reg = +{ + .owner = THIS_MODULE, + .read = snd_pcm_oss_read, + .write = snd_pcm_oss_write, + .open = snd_pcm_oss_open, + .release = snd_pcm_oss_release, + .llseek = no_llseek, + .poll = snd_pcm_oss_poll, + .unlocked_ioctl = snd_pcm_oss_ioctl, + .compat_ioctl = snd_pcm_oss_ioctl_compat, + .mmap = snd_pcm_oss_mmap, +}; + +static void register_oss_dsp(struct snd_pcm *pcm, int index) +{ + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, index, &snd_pcm_oss_f_reg, + pcm) < 0) { + pcm_err(pcm, "unable to register OSS PCM device %i:%i\n", + pcm->card->number, pcm->device); + } +} + +static int snd_pcm_oss_register_minor(struct snd_pcm *pcm) +{ + pcm->oss.reg = 0; + if (dsp_map[pcm->card->number] == (int)pcm->device) { + char name[128]; + int duplex; + register_oss_dsp(pcm, 0); + duplex = (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count > 0 && + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count && + !(pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX)); + sprintf(name, "%s%s", pcm->name, duplex ? " (DUPLEX)" : ""); +#ifdef SNDRV_OSS_INFO_DEV_AUDIO + snd_oss_info_register(SNDRV_OSS_INFO_DEV_AUDIO, + pcm->card->number, + name); +#endif + pcm->oss.reg++; + pcm->oss.reg_mask |= 1; + } + if (adsp_map[pcm->card->number] == (int)pcm->device) { + register_oss_dsp(pcm, 1); + pcm->oss.reg++; + pcm->oss.reg_mask |= 2; + } + + if (pcm->oss.reg) + snd_pcm_oss_proc_init(pcm); + + return 0; +} + +static int snd_pcm_oss_disconnect_minor(struct snd_pcm *pcm) +{ + if (pcm->oss.reg) { + if (pcm->oss.reg_mask & 1) { + pcm->oss.reg_mask &= ~1; + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, 0); + } + if (pcm->oss.reg_mask & 2) { + pcm->oss.reg_mask &= ~2; + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_PCM, + pcm->card, 1); + } + if (dsp_map[pcm->card->number] == (int)pcm->device) { +#ifdef SNDRV_OSS_INFO_DEV_AUDIO + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_AUDIO, pcm->card->number); +#endif + } + pcm->oss.reg = 0; + } + return 0; +} + +static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm) +{ + snd_pcm_oss_disconnect_minor(pcm); + snd_pcm_oss_proc_done(pcm); + return 0; +} + +static struct snd_pcm_notify snd_pcm_oss_notify = +{ + .n_register = snd_pcm_oss_register_minor, + .n_disconnect = snd_pcm_oss_disconnect_minor, + .n_unregister = snd_pcm_oss_unregister_minor, +}; + +static int __init alsa_pcm_oss_init(void) +{ + int i; + int err; + + /* check device map table */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (dsp_map[i] < 0 || dsp_map[i] >= SNDRV_PCM_DEVICES) { + pr_err("ALSA: pcm_oss: invalid dsp_map[%d] = %d\n", + i, dsp_map[i]); + dsp_map[i] = 0; + } + if (adsp_map[i] < 0 || adsp_map[i] >= SNDRV_PCM_DEVICES) { + pr_err("ALSA: pcm_oss: invalid adsp_map[%d] = %d\n", + i, adsp_map[i]); + adsp_map[i] = 1; + } + } + err = snd_pcm_notify(&snd_pcm_oss_notify, 0); + if (err < 0) + return err; + return 0; +} + +static void __exit alsa_pcm_oss_exit(void) +{ + snd_pcm_notify(&snd_pcm_oss_notify, 1); +} + +module_init(alsa_pcm_oss_init) +module_exit(alsa_pcm_oss_exit) diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c new file mode 100644 index 0000000000..82e180c776 --- /dev/null +++ b/sound/core/oss/pcm_plugin.c @@ -0,0 +1,781 @@ +/* + * PCM Plug-In shared (kernel/library) code + * Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#if 0 +#define PLUGIN_DEBUG +#endif + +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/vmalloc.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "pcm_plugin.h" + +#define snd_pcm_plug_first(plug) ((plug)->runtime->oss.plugin_first) +#define snd_pcm_plug_last(plug) ((plug)->runtime->oss.plugin_last) + +/* + * because some cards might have rates "very close", we ignore + * all "resampling" requests within +-5% + */ +static int rate_match(unsigned int src_rate, unsigned int dst_rate) +{ + unsigned int low = (src_rate * 95) / 100; + unsigned int high = (src_rate * 105) / 100; + return dst_rate >= low && dst_rate <= high; +} + +static int snd_pcm_plugin_alloc(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames) +{ + struct snd_pcm_plugin_format *format; + ssize_t width; + size_t size; + unsigned int channel; + struct snd_pcm_plugin_channel *c; + + if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) { + format = &plugin->src_format; + } else { + format = &plugin->dst_format; + } + width = snd_pcm_format_physical_width(format->format); + if (width < 0) + return width; + size = array3_size(frames, format->channels, width); + /* check for too large period size once again */ + if (size > 1024 * 1024) + return -ENOMEM; + if (snd_BUG_ON(size % 8)) + return -ENXIO; + size /= 8; + if (plugin->buf_frames < frames) { + kvfree(plugin->buf); + plugin->buf = kvzalloc(size, GFP_KERNEL); + plugin->buf_frames = frames; + } + if (!plugin->buf) { + plugin->buf_frames = 0; + return -ENOMEM; + } + c = plugin->buf_channels; + if (plugin->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED) { + for (channel = 0; channel < format->channels; channel++, c++) { + c->frames = frames; + c->enabled = 1; + c->wanted = 0; + c->area.addr = plugin->buf; + c->area.first = channel * width; + c->area.step = format->channels * width; + } + } else if (plugin->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) { + if (snd_BUG_ON(size % format->channels)) + return -EINVAL; + size /= format->channels; + for (channel = 0; channel < format->channels; channel++, c++) { + c->frames = frames; + c->enabled = 1; + c->wanted = 0; + c->area.addr = plugin->buf + (channel * size); + c->area.first = 0; + c->area.step = width; + } + } else + return -EINVAL; + return 0; +} + +int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames) +{ + int err; + if (snd_BUG_ON(!snd_pcm_plug_first(plug))) + return -ENXIO; + if (snd_pcm_plug_stream(plug) == SNDRV_PCM_STREAM_PLAYBACK) { + struct snd_pcm_plugin *plugin = snd_pcm_plug_first(plug); + while (plugin->next) { + if (plugin->dst_frames) + frames = plugin->dst_frames(plugin, frames); + if ((snd_pcm_sframes_t)frames <= 0) + return -ENXIO; + plugin = plugin->next; + err = snd_pcm_plugin_alloc(plugin, frames); + if (err < 0) + return err; + } + } else { + struct snd_pcm_plugin *plugin = snd_pcm_plug_last(plug); + while (plugin->prev) { + if (plugin->src_frames) + frames = plugin->src_frames(plugin, frames); + if ((snd_pcm_sframes_t)frames <= 0) + return -ENXIO; + plugin = plugin->prev; + err = snd_pcm_plugin_alloc(plugin, frames); + if (err < 0) + return err; + } + } + return 0; +} + + +snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels) +{ + *channels = plugin->buf_channels; + return frames; +} + +int snd_pcm_plugin_build(struct snd_pcm_substream *plug, + const char *name, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + size_t extra, + struct snd_pcm_plugin **ret) +{ + struct snd_pcm_plugin *plugin; + unsigned int channels; + + if (snd_BUG_ON(!plug)) + return -ENXIO; + if (snd_BUG_ON(!src_format || !dst_format)) + return -ENXIO; + plugin = kzalloc(sizeof(*plugin) + extra, GFP_KERNEL); + if (plugin == NULL) + return -ENOMEM; + plugin->name = name; + plugin->plug = plug; + plugin->stream = snd_pcm_plug_stream(plug); + plugin->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + plugin->src_format = *src_format; + plugin->src_width = snd_pcm_format_physical_width(src_format->format); + snd_BUG_ON(plugin->src_width <= 0); + plugin->dst_format = *dst_format; + plugin->dst_width = snd_pcm_format_physical_width(dst_format->format); + snd_BUG_ON(plugin->dst_width <= 0); + if (plugin->stream == SNDRV_PCM_STREAM_PLAYBACK) + channels = src_format->channels; + else + channels = dst_format->channels; + plugin->buf_channels = kcalloc(channels, sizeof(*plugin->buf_channels), GFP_KERNEL); + if (plugin->buf_channels == NULL) { + snd_pcm_plugin_free(plugin); + return -ENOMEM; + } + plugin->client_channels = snd_pcm_plugin_client_channels; + *ret = plugin; + return 0; +} + +int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin) +{ + if (! plugin) + return 0; + if (plugin->private_free) + plugin->private_free(plugin); + kfree(plugin->buf_channels); + kvfree(plugin->buf); + kfree(plugin); + return 0; +} + +static snd_pcm_sframes_t calc_dst_frames(struct snd_pcm_substream *plug, + snd_pcm_sframes_t frames, + bool check_size) +{ + struct snd_pcm_plugin *plugin, *plugin_next; + + plugin = snd_pcm_plug_first(plug); + while (plugin && frames > 0) { + plugin_next = plugin->next; + if (check_size && plugin->buf_frames && + frames > plugin->buf_frames) + frames = plugin->buf_frames; + if (plugin->dst_frames) { + frames = plugin->dst_frames(plugin, frames); + if (frames < 0) + return frames; + } + plugin = plugin_next; + } + return frames; +} + +static snd_pcm_sframes_t calc_src_frames(struct snd_pcm_substream *plug, + snd_pcm_sframes_t frames, + bool check_size) +{ + struct snd_pcm_plugin *plugin, *plugin_prev; + + plugin = snd_pcm_plug_last(plug); + while (plugin && frames > 0) { + plugin_prev = plugin->prev; + if (plugin->src_frames) { + frames = plugin->src_frames(plugin, frames); + if (frames < 0) + return frames; + } + if (check_size && plugin->buf_frames && + frames > plugin->buf_frames) + frames = plugin->buf_frames; + plugin = plugin_prev; + } + return frames; +} + +snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t drv_frames) +{ + if (snd_BUG_ON(!plug)) + return -ENXIO; + switch (snd_pcm_plug_stream(plug)) { + case SNDRV_PCM_STREAM_PLAYBACK: + return calc_src_frames(plug, drv_frames, false); + case SNDRV_PCM_STREAM_CAPTURE: + return calc_dst_frames(plug, drv_frames, false); + default: + snd_BUG(); + return -EINVAL; + } +} + +snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *plug, snd_pcm_uframes_t clt_frames) +{ + if (snd_BUG_ON(!plug)) + return -ENXIO; + switch (snd_pcm_plug_stream(plug)) { + case SNDRV_PCM_STREAM_PLAYBACK: + return calc_dst_frames(plug, clt_frames, false); + case SNDRV_PCM_STREAM_CAPTURE: + return calc_src_frames(plug, clt_frames, false); + default: + snd_BUG(); + return -EINVAL; + } +} + +static int snd_pcm_plug_formats(const struct snd_mask *mask, + snd_pcm_format_t format) +{ + struct snd_mask formats = *mask; + u64 linfmts = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_BE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_U24_BE | SNDRV_PCM_FMTBIT_S24_BE | + SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_U24_3BE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_S32_LE | + SNDRV_PCM_FMTBIT_U32_BE | SNDRV_PCM_FMTBIT_S32_BE); + snd_mask_set(&formats, (__force int)SNDRV_PCM_FORMAT_MU_LAW); + + if (formats.bits[0] & lower_32_bits(linfmts)) + formats.bits[0] |= lower_32_bits(linfmts); + if (formats.bits[1] & upper_32_bits(linfmts)) + formats.bits[1] |= upper_32_bits(linfmts); + return snd_mask_test(&formats, (__force int)format); +} + +static const snd_pcm_format_t preferred_formats[] = { + SNDRV_PCM_FORMAT_S16_LE, + SNDRV_PCM_FORMAT_S16_BE, + SNDRV_PCM_FORMAT_U16_LE, + SNDRV_PCM_FORMAT_U16_BE, + SNDRV_PCM_FORMAT_S24_3LE, + SNDRV_PCM_FORMAT_S24_3BE, + SNDRV_PCM_FORMAT_U24_3LE, + SNDRV_PCM_FORMAT_U24_3BE, + SNDRV_PCM_FORMAT_S24_LE, + SNDRV_PCM_FORMAT_S24_BE, + SNDRV_PCM_FORMAT_U24_LE, + SNDRV_PCM_FORMAT_U24_BE, + SNDRV_PCM_FORMAT_S32_LE, + SNDRV_PCM_FORMAT_S32_BE, + SNDRV_PCM_FORMAT_U32_LE, + SNDRV_PCM_FORMAT_U32_BE, + SNDRV_PCM_FORMAT_S8, + SNDRV_PCM_FORMAT_U8 +}; + +snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + const struct snd_mask *format_mask) +{ + int i; + + if (snd_mask_test(format_mask, (__force int)format)) + return format; + if (!snd_pcm_plug_formats(format_mask, format)) + return (__force snd_pcm_format_t)-EINVAL; + if (snd_pcm_format_linear(format)) { + unsigned int width = snd_pcm_format_width(format); + int unsignd = snd_pcm_format_unsigned(format) > 0; + int big = snd_pcm_format_big_endian(format) > 0; + unsigned int badness, best = -1; + snd_pcm_format_t best_format = (__force snd_pcm_format_t)-1; + for (i = 0; i < ARRAY_SIZE(preferred_formats); i++) { + snd_pcm_format_t f = preferred_formats[i]; + unsigned int w; + if (!snd_mask_test(format_mask, (__force int)f)) + continue; + w = snd_pcm_format_width(f); + if (w >= width) + badness = w - width; + else + badness = width - w + 32; + badness += snd_pcm_format_unsigned(f) != unsignd; + badness += snd_pcm_format_big_endian(f) != big; + if (badness < best) { + best_format = f; + best = badness; + } + } + if ((__force int)best_format >= 0) + return best_format; + else + return (__force snd_pcm_format_t)-EINVAL; + } else { + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + for (i = 0; i < ARRAY_SIZE(preferred_formats); ++i) { + snd_pcm_format_t format1 = preferred_formats[i]; + if (snd_mask_test(format_mask, (__force int)format1)) + return format1; + } + fallthrough; + default: + return (__force snd_pcm_format_t)-EINVAL; + } + } +} + +int snd_pcm_plug_format_plugins(struct snd_pcm_substream *plug, + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_params *slave_params) +{ + struct snd_pcm_plugin_format tmpformat; + struct snd_pcm_plugin_format dstformat; + struct snd_pcm_plugin_format srcformat; + snd_pcm_access_t src_access, dst_access; + struct snd_pcm_plugin *plugin = NULL; + int err; + int stream = snd_pcm_plug_stream(plug); + int slave_interleaved = (params_channels(slave_params) == 1 || + params_access(slave_params) == SNDRV_PCM_ACCESS_RW_INTERLEAVED); + + switch (stream) { + case SNDRV_PCM_STREAM_PLAYBACK: + dstformat.format = params_format(slave_params); + dstformat.rate = params_rate(slave_params); + dstformat.channels = params_channels(slave_params); + srcformat.format = params_format(params); + srcformat.rate = params_rate(params); + srcformat.channels = params_channels(params); + src_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + dst_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED : + SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + break; + case SNDRV_PCM_STREAM_CAPTURE: + dstformat.format = params_format(params); + dstformat.rate = params_rate(params); + dstformat.channels = params_channels(params); + srcformat.format = params_format(slave_params); + srcformat.rate = params_rate(slave_params); + srcformat.channels = params_channels(slave_params); + src_access = (slave_interleaved ? SNDRV_PCM_ACCESS_RW_INTERLEAVED : + SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + dst_access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + break; + default: + snd_BUG(); + return -EINVAL; + } + tmpformat = srcformat; + + pdprintf("srcformat: format=%i, rate=%i, channels=%i\n", + srcformat.format, + srcformat.rate, + srcformat.channels); + pdprintf("dstformat: format=%i, rate=%i, channels=%i\n", + dstformat.format, + dstformat.rate, + dstformat.channels); + + /* Format change (linearization) */ + if (! rate_match(srcformat.rate, dstformat.rate) && + ! snd_pcm_format_linear(srcformat.format)) { + if (srcformat.format != SNDRV_PCM_FORMAT_MU_LAW) + return -EINVAL; + tmpformat.format = SNDRV_PCM_FORMAT_S16; + err = snd_pcm_plugin_build_mulaw(plug, + &srcformat, &tmpformat, + &plugin); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* channels reduction */ + if (srcformat.channels > dstformat.channels) { + tmpformat.channels = dstformat.channels; + err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin); + pdprintf("channels reduction: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* rate resampling */ + if (!rate_match(srcformat.rate, dstformat.rate)) { + if (srcformat.format != SNDRV_PCM_FORMAT_S16) { + /* convert to S16 for resampling */ + tmpformat.format = SNDRV_PCM_FORMAT_S16; + err = snd_pcm_plugin_build_linear(plug, + &srcformat, &tmpformat, + &plugin); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + tmpformat.rate = dstformat.rate; + err = snd_pcm_plugin_build_rate(plug, + &srcformat, &tmpformat, + &plugin); + pdprintf("rate down resampling: src=%i, dst=%i returns %i\n", srcformat.rate, tmpformat.rate, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* format change */ + if (srcformat.format != dstformat.format) { + tmpformat.format = dstformat.format; + if (srcformat.format == SNDRV_PCM_FORMAT_MU_LAW || + tmpformat.format == SNDRV_PCM_FORMAT_MU_LAW) { + err = snd_pcm_plugin_build_mulaw(plug, + &srcformat, &tmpformat, + &plugin); + } + else if (snd_pcm_format_linear(srcformat.format) && + snd_pcm_format_linear(tmpformat.format)) { + err = snd_pcm_plugin_build_linear(plug, + &srcformat, &tmpformat, + &plugin); + } + else + return -EINVAL; + pdprintf("format change: src=%i, dst=%i returns %i\n", srcformat.format, tmpformat.format, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* channels extension */ + if (srcformat.channels < dstformat.channels) { + tmpformat.channels = dstformat.channels; + err = snd_pcm_plugin_build_route(plug, &srcformat, &tmpformat, &plugin); + pdprintf("channels extension: src=%i, dst=%i returns %i\n", srcformat.channels, tmpformat.channels, err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + srcformat = tmpformat; + src_access = dst_access; + } + + /* de-interleave */ + if (src_access != dst_access) { + err = snd_pcm_plugin_build_copy(plug, + &srcformat, + &tmpformat, + &plugin); + pdprintf("interleave change (copy: returns %i)\n", err); + if (err < 0) + return err; + err = snd_pcm_plugin_append(plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + } + + return 0; +} + +snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *plug, + char *buf, + snd_pcm_uframes_t count, + struct snd_pcm_plugin_channel **channels) +{ + struct snd_pcm_plugin *plugin; + struct snd_pcm_plugin_channel *v; + struct snd_pcm_plugin_format *format; + int width, nchannels, channel; + int stream = snd_pcm_plug_stream(plug); + + if (snd_BUG_ON(!buf)) + return -ENXIO; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + plugin = snd_pcm_plug_first(plug); + format = &plugin->src_format; + } else { + plugin = snd_pcm_plug_last(plug); + format = &plugin->dst_format; + } + v = plugin->buf_channels; + *channels = v; + width = snd_pcm_format_physical_width(format->format); + if (width < 0) + return width; + nchannels = format->channels; + if (snd_BUG_ON(plugin->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED && + format->channels > 1)) + return -ENXIO; + for (channel = 0; channel < nchannels; channel++, v++) { + v->frames = count; + v->enabled = 1; + v->wanted = (stream == SNDRV_PCM_STREAM_CAPTURE); + v->area.addr = buf; + v->area.first = channel * width; + v->area.step = nchannels * width; + } + return count; +} + +snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *src_channels, snd_pcm_uframes_t size) +{ + struct snd_pcm_plugin *plugin, *next; + struct snd_pcm_plugin_channel *dst_channels; + int err; + snd_pcm_sframes_t frames = size; + + plugin = snd_pcm_plug_first(plug); + while (plugin) { + if (frames <= 0) + return frames; + next = plugin->next; + if (next) { + snd_pcm_sframes_t frames1 = frames; + if (plugin->dst_frames) { + frames1 = plugin->dst_frames(plugin, frames); + if (frames1 <= 0) + return frames1; + } + err = next->client_channels(next, frames1, &dst_channels); + if (err < 0) + return err; + if (err != frames1) { + frames = err; + if (plugin->src_frames) { + frames = plugin->src_frames(plugin, frames1); + if (frames <= 0) + return frames; + } + } + } else + dst_channels = NULL; + pdprintf("write plugin: %s, %li\n", plugin->name, frames); + frames = plugin->transfer(plugin, src_channels, dst_channels, frames); + if (frames < 0) + return frames; + src_channels = dst_channels; + plugin = next; + } + return calc_src_frames(plug, frames, true); +} + +snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *plug, struct snd_pcm_plugin_channel *dst_channels_final, snd_pcm_uframes_t size) +{ + struct snd_pcm_plugin *plugin, *next; + struct snd_pcm_plugin_channel *src_channels, *dst_channels; + snd_pcm_sframes_t frames = size; + int err; + + frames = calc_src_frames(plug, frames, true); + if (frames < 0) + return frames; + + src_channels = NULL; + plugin = snd_pcm_plug_first(plug); + while (plugin && frames > 0) { + next = plugin->next; + if (next) { + err = plugin->client_channels(plugin, frames, &dst_channels); + if (err < 0) + return err; + frames = err; + } else { + dst_channels = dst_channels_final; + } + pdprintf("read plugin: %s, %li\n", plugin->name, frames); + frames = plugin->transfer(plugin, src_channels, dst_channels, frames); + if (frames < 0) + return frames; + plugin = next; + src_channels = dst_channels; + } + return frames; +} + +int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_area, size_t dst_offset, + size_t samples, snd_pcm_format_t format) +{ + /* FIXME: sub byte resolution and odd dst_offset */ + unsigned char *dst; + unsigned int dst_step; + int width; + const unsigned char *silence; + if (!dst_area->addr) + return 0; + dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8; + width = snd_pcm_format_physical_width(format); + if (width <= 0) + return -EINVAL; + if (dst_area->step == (unsigned int) width && width >= 8) + return snd_pcm_format_set_silence(format, dst, samples); + silence = snd_pcm_format_silence_64(format); + if (! silence) + return -EINVAL; + dst_step = dst_area->step / 8; + if (width == 4) { + /* Ima ADPCM */ + int dstbit = dst_area->first % 8; + int dstbit_step = dst_area->step % 8; + while (samples-- > 0) { + if (dstbit) + *dst &= 0xf0; + else + *dst &= 0x0f; + dst += dst_step; + dstbit += dstbit_step; + if (dstbit == 8) { + dst++; + dstbit = 0; + } + } + } else { + width /= 8; + while (samples-- > 0) { + memcpy(dst, silence, width); + dst += dst_step; + } + } + return 0; +} + +int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_area, size_t src_offset, + const struct snd_pcm_channel_area *dst_area, size_t dst_offset, + size_t samples, snd_pcm_format_t format) +{ + /* FIXME: sub byte resolution and odd dst_offset */ + char *src, *dst; + int width; + int src_step, dst_step; + src = src_area->addr + (src_area->first + src_area->step * src_offset) / 8; + if (!src_area->addr) + return snd_pcm_area_silence(dst_area, dst_offset, samples, format); + dst = dst_area->addr + (dst_area->first + dst_area->step * dst_offset) / 8; + if (!dst_area->addr) + return 0; + width = snd_pcm_format_physical_width(format); + if (width <= 0) + return -EINVAL; + if (src_area->step == (unsigned int) width && + dst_area->step == (unsigned int) width && width >= 8) { + size_t bytes = samples * width / 8; + memcpy(dst, src, bytes); + return 0; + } + src_step = src_area->step / 8; + dst_step = dst_area->step / 8; + if (width == 4) { + /* Ima ADPCM */ + int srcbit = src_area->first % 8; + int srcbit_step = src_area->step % 8; + int dstbit = dst_area->first % 8; + int dstbit_step = dst_area->step % 8; + while (samples-- > 0) { + unsigned char srcval; + if (srcbit) + srcval = *src & 0x0f; + else + srcval = (*src & 0xf0) >> 4; + if (dstbit) + *dst = (*dst & 0xf0) | srcval; + else + *dst = (*dst & 0x0f) | (srcval << 4); + src += src_step; + srcbit += srcbit_step; + if (srcbit == 8) { + src++; + srcbit = 0; + } + dst += dst_step; + dstbit += dstbit_step; + if (dstbit == 8) { + dst++; + dstbit = 0; + } + } + } else { + width /= 8; + while (samples-- > 0) { + memcpy(dst, src, width); + src += src_step; + dst += dst_step; + } + } + return 0; +} diff --git a/sound/core/oss/pcm_plugin.h b/sound/core/oss/pcm_plugin.h new file mode 100644 index 0000000000..50a6b50f5d --- /dev/null +++ b/sound/core/oss/pcm_plugin.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __PCM_PLUGIN_H +#define __PCM_PLUGIN_H + +/* + * Digital Audio (Plugin interface) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#ifdef CONFIG_SND_PCM_OSS_PLUGINS + +#define snd_pcm_plug_stream(plug) ((plug)->stream) + +enum snd_pcm_plugin_action { + INIT = 0, + PREPARE = 1, +}; + +struct snd_pcm_channel_area { + void *addr; /* base address of channel samples */ + unsigned int first; /* offset to first sample in bits */ + unsigned int step; /* samples distance in bits */ +}; + +struct snd_pcm_plugin_channel { + void *aptr; /* pointer to the allocated area */ + struct snd_pcm_channel_area area; + snd_pcm_uframes_t frames; /* allocated frames */ + unsigned int enabled:1; /* channel need to be processed */ + unsigned int wanted:1; /* channel is wanted */ +}; + +struct snd_pcm_plugin_format { + snd_pcm_format_t format; + unsigned int rate; + unsigned int channels; +}; + +struct snd_pcm_plugin { + const char *name; /* plug-in name */ + int stream; + struct snd_pcm_plugin_format src_format; /* source format */ + struct snd_pcm_plugin_format dst_format; /* destination format */ + int src_width; /* sample width in bits */ + int dst_width; /* sample width in bits */ + snd_pcm_access_t access; + snd_pcm_sframes_t (*src_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t dst_frames); + snd_pcm_sframes_t (*dst_frames)(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t src_frames); + snd_pcm_sframes_t (*client_channels)(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels); + snd_pcm_sframes_t (*transfer)(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames); + int (*action)(struct snd_pcm_plugin *plugin, + enum snd_pcm_plugin_action action, + unsigned long data); + struct snd_pcm_plugin *prev; + struct snd_pcm_plugin *next; + struct snd_pcm_substream *plug; + void *private_data; + void (*private_free)(struct snd_pcm_plugin *plugin); + char *buf; + snd_pcm_uframes_t buf_frames; + struct snd_pcm_plugin_channel *buf_channels; + char extra_data[]; +}; + +int snd_pcm_plugin_build(struct snd_pcm_substream *handle, + const char *name, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + size_t extra, + struct snd_pcm_plugin **ret); +int snd_pcm_plugin_free(struct snd_pcm_plugin *plugin); +int snd_pcm_plugin_clear(struct snd_pcm_plugin **first); +int snd_pcm_plug_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t frames); +snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size); +snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size); + +#define FULL ROUTE_PLUGIN_RESOLUTION +#define HALF ROUTE_PLUGIN_RESOLUTION / 2 + +int snd_pcm_plugin_build_io(struct snd_pcm_substream *handle, + struct snd_pcm_hw_params *params, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_linear(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_mulaw(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_rate(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_route(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); +int snd_pcm_plugin_build_copy(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin); + +int snd_pcm_plug_format_plugins(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_pcm_hw_params *slave_params); + +snd_pcm_format_t snd_pcm_plug_slave_format(snd_pcm_format_t format, + const struct snd_mask *format_mask); + +int snd_pcm_plugin_append(struct snd_pcm_plugin *plugin); + +snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_channel *src_channels, + snd_pcm_uframes_t size); +snd_pcm_sframes_t snd_pcm_plug_read_transfer(struct snd_pcm_substream *handle, + struct snd_pcm_plugin_channel *dst_channels_final, + snd_pcm_uframes_t size); + +snd_pcm_sframes_t snd_pcm_plug_client_channels_buf(struct snd_pcm_substream *handle, + char *buf, snd_pcm_uframes_t count, + struct snd_pcm_plugin_channel **channels); + +snd_pcm_sframes_t snd_pcm_plugin_client_channels(struct snd_pcm_plugin *plugin, + snd_pcm_uframes_t frames, + struct snd_pcm_plugin_channel **channels); + +int snd_pcm_area_silence(const struct snd_pcm_channel_area *dst_channel, + size_t dst_offset, + size_t samples, snd_pcm_format_t format); +int snd_pcm_area_copy(const struct snd_pcm_channel_area *src_channel, + size_t src_offset, + const struct snd_pcm_channel_area *dst_channel, + size_t dst_offset, + size_t samples, snd_pcm_format_t format); + +void *snd_pcm_plug_buf_alloc(struct snd_pcm_substream *plug, snd_pcm_uframes_t size); +void snd_pcm_plug_buf_unlock(struct snd_pcm_substream *plug, void *ptr); +#else + +static inline snd_pcm_sframes_t snd_pcm_plug_client_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t drv_size) { return drv_size; } +static inline snd_pcm_sframes_t snd_pcm_plug_slave_size(struct snd_pcm_substream *handle, snd_pcm_uframes_t clt_size) { return clt_size; } +static inline int snd_pcm_plug_slave_format(int format, const struct snd_mask *format_mask) { return format; } + +#endif + +snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, + const char *ptr, snd_pcm_uframes_t size, + int in_kernel); +snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, + char *ptr, snd_pcm_uframes_t size, int in_kernel); +snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, + void **bufs, snd_pcm_uframes_t frames); +snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream, + void **bufs, snd_pcm_uframes_t frames); + +#ifdef PLUGIN_DEBUG +#define pdprintf(fmt, args...) printk(KERN_DEBUG "plugin: " fmt, ##args) +#else +#define pdprintf(fmt, args...) +#endif + +#endif /* __PCM_PLUGIN_H */ diff --git a/sound/core/oss/rate.c b/sound/core/oss/rate.c new file mode 100644 index 0000000000..9826911934 --- /dev/null +++ b/sound/core/oss/rate.c @@ -0,0 +1,348 @@ +/* + * Rate conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +#define SHIFT 11 +#define BITS (1<<SHIFT) +#define R_MASK (BITS-1) + +/* + * Basic rate conversion plugin + */ + +struct rate_channel { + signed short last_S1; + signed short last_S2; +}; + +typedef void (*rate_f)(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + int src_frames, int dst_frames); + +struct rate_priv { + unsigned int pitch; + unsigned int pos; + rate_f func; + snd_pcm_sframes_t old_src_frames, old_dst_frames; + struct rate_channel channels[]; +}; + +static void rate_init(struct snd_pcm_plugin *plugin) +{ + unsigned int channel; + struct rate_priv *data = (struct rate_priv *)plugin->extra_data; + data->pos = 0; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + data->channels[channel].last_S1 = 0; + data->channels[channel].last_S2 = 0; + } +} + +static void resample_expand(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + int src_frames, int dst_frames) +{ + unsigned int pos = 0; + signed int val; + signed short S1, S2; + signed short *src, *dst; + unsigned int channel; + int src_step, dst_step; + int src_frames1, dst_frames1; + struct rate_priv *data = (struct rate_priv *)plugin->extra_data; + struct rate_channel *rchannels = data->channels; + + for (channel = 0; channel < plugin->src_format.channels; channel++) { + pos = data->pos; + S1 = rchannels->last_S1; + S2 = rchannels->last_S2; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = (signed short *)src_channels[channel].area.addr + + src_channels[channel].area.first / 8 / 2; + dst = (signed short *)dst_channels[channel].area.addr + + dst_channels[channel].area.first / 8 / 2; + src_step = src_channels[channel].area.step / 8 / 2; + dst_step = dst_channels[channel].area.step / 8 / 2; + src_frames1 = src_frames; + dst_frames1 = dst_frames; + while (dst_frames1-- > 0) { + if (pos & ~R_MASK) { + pos &= R_MASK; + S1 = S2; + if (src_frames1-- > 0) { + S2 = *src; + src += src_step; + } + } + val = S1 + ((S2 - S1) * (signed int)pos) / BITS; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + *dst = val; + dst += dst_step; + pos += data->pitch; + } + rchannels->last_S1 = S1; + rchannels->last_S2 = S2; + rchannels++; + } + data->pos = pos; +} + +static void resample_shrink(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + int src_frames, int dst_frames) +{ + unsigned int pos = 0; + signed int val; + signed short S1, S2; + signed short *src, *dst; + unsigned int channel; + int src_step, dst_step; + int src_frames1, dst_frames1; + struct rate_priv *data = (struct rate_priv *)plugin->extra_data; + struct rate_channel *rchannels = data->channels; + + for (channel = 0; channel < plugin->src_format.channels; ++channel) { + pos = data->pos; + S1 = rchannels->last_S1; + S2 = rchannels->last_S2; + if (!src_channels[channel].enabled) { + if (dst_channels[channel].wanted) + snd_pcm_area_silence(&dst_channels[channel].area, 0, dst_frames, plugin->dst_format.format); + dst_channels[channel].enabled = 0; + continue; + } + dst_channels[channel].enabled = 1; + src = (signed short *)src_channels[channel].area.addr + + src_channels[channel].area.first / 8 / 2; + dst = (signed short *)dst_channels[channel].area.addr + + dst_channels[channel].area.first / 8 / 2; + src_step = src_channels[channel].area.step / 8 / 2; + dst_step = dst_channels[channel].area.step / 8 / 2; + src_frames1 = src_frames; + dst_frames1 = dst_frames; + while (dst_frames1 > 0) { + S1 = S2; + if (src_frames1-- > 0) { + S2 = *src; + src += src_step; + } + if (pos & ~R_MASK) { + pos &= R_MASK; + val = S1 + ((S2 - S1) * (signed int)pos) / BITS; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + *dst = val; + dst += dst_step; + dst_frames1--; + } + pos += data->pitch; + } + rchannels->last_S1 = S1; + rchannels->last_S2 = S2; + rchannels++; + } + data->pos = pos; +} + +static snd_pcm_sframes_t rate_src_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames) +{ + struct rate_priv *data; + snd_pcm_sframes_t res; + + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (frames == 0) + return 0; + data = (struct rate_priv *)plugin->extra_data; + if (plugin->src_format.rate < plugin->dst_format.rate) { + res = (((frames * data->pitch) + (BITS/2)) >> SHIFT); + } else { + res = DIV_ROUND_CLOSEST(frames << SHIFT, data->pitch); + } + if (data->old_src_frames > 0) { + snd_pcm_sframes_t frames1 = frames, res1 = data->old_dst_frames; + while (data->old_src_frames < frames1) { + frames1 >>= 1; + res1 <<= 1; + } + while (data->old_src_frames > frames1) { + frames1 <<= 1; + res1 >>= 1; + } + if (data->old_src_frames == frames1) + return res1; + } + data->old_src_frames = frames; + data->old_dst_frames = res; + return res; +} + +static snd_pcm_sframes_t rate_dst_frames(struct snd_pcm_plugin *plugin, snd_pcm_uframes_t frames) +{ + struct rate_priv *data; + snd_pcm_sframes_t res; + + if (snd_BUG_ON(!plugin)) + return -ENXIO; + if (frames == 0) + return 0; + data = (struct rate_priv *)plugin->extra_data; + if (plugin->src_format.rate < plugin->dst_format.rate) { + res = DIV_ROUND_CLOSEST(frames << SHIFT, data->pitch); + } else { + res = (((frames * data->pitch) + (BITS/2)) >> SHIFT); + } + if (data->old_dst_frames > 0) { + snd_pcm_sframes_t frames1 = frames, res1 = data->old_src_frames; + while (data->old_dst_frames < frames1) { + frames1 >>= 1; + res1 <<= 1; + } + while (data->old_dst_frames > frames1) { + frames1 <<= 1; + res1 >>= 1; + } + if (data->old_dst_frames == frames1) + return res1; + } + data->old_dst_frames = frames; + data->old_src_frames = res; + return res; +} + +static snd_pcm_sframes_t rate_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + snd_pcm_uframes_t dst_frames; + struct rate_priv *data; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; +#ifdef CONFIG_SND_DEBUG + { + unsigned int channel; + for (channel = 0; channel < plugin->src_format.channels; channel++) { + if (snd_BUG_ON(src_channels[channel].area.first % 8 || + src_channels[channel].area.step % 8)) + return -ENXIO; + if (snd_BUG_ON(dst_channels[channel].area.first % 8 || + dst_channels[channel].area.step % 8)) + return -ENXIO; + } + } +#endif + + dst_frames = rate_dst_frames(plugin, frames); + if (dst_frames > dst_channels[0].frames) + dst_frames = dst_channels[0].frames; + data = (struct rate_priv *)plugin->extra_data; + data->func(plugin, src_channels, dst_channels, frames, dst_frames); + return dst_frames; +} + +static int rate_action(struct snd_pcm_plugin *plugin, + enum snd_pcm_plugin_action action, + unsigned long udata) +{ + if (snd_BUG_ON(!plugin)) + return -ENXIO; + switch (action) { + case INIT: + case PREPARE: + rate_init(plugin); + break; + default: + break; + } + return 0; /* silenty ignore other actions */ +} + +int snd_pcm_plugin_build_rate(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + int err; + struct rate_priv *data; + struct snd_pcm_plugin *plugin; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + + if (snd_BUG_ON(src_format->channels != dst_format->channels)) + return -ENXIO; + if (snd_BUG_ON(src_format->channels <= 0)) + return -ENXIO; + if (snd_BUG_ON(src_format->format != SNDRV_PCM_FORMAT_S16)) + return -ENXIO; + if (snd_BUG_ON(dst_format->format != SNDRV_PCM_FORMAT_S16)) + return -ENXIO; + if (snd_BUG_ON(src_format->rate == dst_format->rate)) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "rate conversion", + src_format, dst_format, + struct_size(data, channels, + src_format->channels), + &plugin); + if (err < 0) + return err; + data = (struct rate_priv *)plugin->extra_data; + if (src_format->rate < dst_format->rate) { + data->pitch = ((src_format->rate << SHIFT) + (dst_format->rate >> 1)) / dst_format->rate; + data->func = resample_expand; + } else { + data->pitch = ((dst_format->rate << SHIFT) + (src_format->rate >> 1)) / src_format->rate; + data->func = resample_shrink; + } + data->pos = 0; + rate_init(plugin); + data->old_src_frames = data->old_dst_frames = 0; + plugin->transfer = rate_transfer; + plugin->src_frames = rate_src_frames; + plugin->dst_frames = rate_dst_frames; + plugin->action = rate_action; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/oss/route.c b/sound/core/oss/route.c new file mode 100644 index 0000000000..72dea04197 --- /dev/null +++ b/sound/core/oss/route.c @@ -0,0 +1,111 @@ +/* + * Route Plug-In + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/time.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include "pcm_plugin.h" + +static void zero_areas(struct snd_pcm_plugin_channel *dvp, int ndsts, + snd_pcm_uframes_t frames, snd_pcm_format_t format) +{ + int dst = 0; + for (; dst < ndsts; ++dst) { + if (dvp->wanted) + snd_pcm_area_silence(&dvp->area, 0, frames, format); + dvp->enabled = 0; + dvp++; + } +} + +static inline void copy_area(const struct snd_pcm_plugin_channel *src_channel, + struct snd_pcm_plugin_channel *dst_channel, + snd_pcm_uframes_t frames, snd_pcm_format_t format) +{ + dst_channel->enabled = 1; + snd_pcm_area_copy(&src_channel->area, 0, &dst_channel->area, 0, frames, format); +} + +static snd_pcm_sframes_t route_transfer(struct snd_pcm_plugin *plugin, + const struct snd_pcm_plugin_channel *src_channels, + struct snd_pcm_plugin_channel *dst_channels, + snd_pcm_uframes_t frames) +{ + int nsrcs, ndsts, dst; + struct snd_pcm_plugin_channel *dvp; + snd_pcm_format_t format; + + if (snd_BUG_ON(!plugin || !src_channels || !dst_channels)) + return -ENXIO; + if (frames == 0) + return 0; + if (frames > dst_channels[0].frames) + frames = dst_channels[0].frames; + + nsrcs = plugin->src_format.channels; + ndsts = plugin->dst_format.channels; + + format = plugin->dst_format.format; + dvp = dst_channels; + if (nsrcs <= 1) { + /* expand to all channels */ + for (dst = 0; dst < ndsts; ++dst) { + copy_area(src_channels, dvp, frames, format); + dvp++; + } + return frames; + } + + for (dst = 0; dst < ndsts && dst < nsrcs; ++dst) { + copy_area(src_channels, dvp, frames, format); + dvp++; + src_channels++; + } + if (dst < ndsts) + zero_areas(dvp, ndsts - dst, frames, format); + return frames; +} + +int snd_pcm_plugin_build_route(struct snd_pcm_substream *plug, + struct snd_pcm_plugin_format *src_format, + struct snd_pcm_plugin_format *dst_format, + struct snd_pcm_plugin **r_plugin) +{ + struct snd_pcm_plugin *plugin; + int err; + + if (snd_BUG_ON(!r_plugin)) + return -ENXIO; + *r_plugin = NULL; + if (snd_BUG_ON(src_format->rate != dst_format->rate)) + return -ENXIO; + if (snd_BUG_ON(src_format->format != dst_format->format)) + return -ENXIO; + + err = snd_pcm_plugin_build(plug, "route conversion", + src_format, dst_format, 0, &plugin); + if (err < 0) + return err; + + plugin->transfer = route_transfer; + *r_plugin = plugin; + return 0; +} diff --git a/sound/core/pcm.c b/sound/core/pcm.c new file mode 100644 index 0000000000..6d0c9c3779 --- /dev/null +++ b/sound/core/pcm.c @@ -0,0 +1,1249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/time.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/nospec.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/pcm.h> +#include <sound/timer.h> +#include <sound/control.h> +#include <sound/info.h> + +#include "pcm_local.h" + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Abramo Bagnara <abramo@alsa-project.org>"); +MODULE_DESCRIPTION("Midlevel PCM code for ALSA."); +MODULE_LICENSE("GPL"); + +static LIST_HEAD(snd_pcm_devices); +static DEFINE_MUTEX(register_mutex); +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +static LIST_HEAD(snd_pcm_notify_list); +#endif + +static int snd_pcm_free(struct snd_pcm *pcm); +static int snd_pcm_dev_free(struct snd_device *device); +static int snd_pcm_dev_register(struct snd_device *device); +static int snd_pcm_dev_disconnect(struct snd_device *device); + +static struct snd_pcm *snd_pcm_get(struct snd_card *card, int device) +{ + struct snd_pcm *pcm; + + list_for_each_entry(pcm, &snd_pcm_devices, list) { + if (pcm->card == card && pcm->device == device) + return pcm; + } + return NULL; +} + +static int snd_pcm_next(struct snd_card *card, int device) +{ + struct snd_pcm *pcm; + + list_for_each_entry(pcm, &snd_pcm_devices, list) { + if (pcm->card == card && pcm->device > device) + return pcm->device; + else if (pcm->card->number > card->number) + return -1; + } + return -1; +} + +static int snd_pcm_add(struct snd_pcm *newpcm) +{ + struct snd_pcm *pcm; + + if (newpcm->internal) + return 0; + + list_for_each_entry(pcm, &snd_pcm_devices, list) { + if (pcm->card == newpcm->card && pcm->device == newpcm->device) + return -EBUSY; + if (pcm->card->number > newpcm->card->number || + (pcm->card == newpcm->card && + pcm->device > newpcm->device)) { + list_add(&newpcm->list, pcm->list.prev); + return 0; + } + } + list_add_tail(&newpcm->list, &snd_pcm_devices); + return 0; +} + +static int snd_pcm_control_ioctl(struct snd_card *card, + struct snd_ctl_file *control, + unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE: + { + int device; + + if (get_user(device, (int __user *)arg)) + return -EFAULT; + mutex_lock(®ister_mutex); + device = snd_pcm_next(card, device); + mutex_unlock(®ister_mutex); + if (put_user(device, (int __user *)arg)) + return -EFAULT; + return 0; + } + case SNDRV_CTL_IOCTL_PCM_INFO: + { + struct snd_pcm_info __user *info; + unsigned int device, subdevice; + int stream; + struct snd_pcm *pcm; + struct snd_pcm_str *pstr; + struct snd_pcm_substream *substream; + int err; + + info = (struct snd_pcm_info __user *)arg; + if (get_user(device, &info->device)) + return -EFAULT; + if (get_user(stream, &info->stream)) + return -EFAULT; + if (stream < 0 || stream > 1) + return -EINVAL; + stream = array_index_nospec(stream, 2); + if (get_user(subdevice, &info->subdevice)) + return -EFAULT; + mutex_lock(®ister_mutex); + pcm = snd_pcm_get(card, device); + if (pcm == NULL) { + err = -ENXIO; + goto _error; + } + pstr = &pcm->streams[stream]; + if (pstr->substream_count == 0) { + err = -ENOENT; + goto _error; + } + if (subdevice >= pstr->substream_count) { + err = -ENXIO; + goto _error; + } + for (substream = pstr->substream; substream; + substream = substream->next) + if (substream->number == (int)subdevice) + break; + if (substream == NULL) { + err = -ENXIO; + goto _error; + } + mutex_lock(&pcm->open_mutex); + err = snd_pcm_info_user(substream, info); + mutex_unlock(&pcm->open_mutex); + _error: + mutex_unlock(®ister_mutex); + return err; + } + case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE: + { + int val; + + if (get_user(val, (int __user *)arg)) + return -EFAULT; + control->preferred_subdevice[SND_CTL_SUBDEV_PCM] = val; + return 0; + } + } + return -ENOIOCTLCMD; +} + +#define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v + +static const char * const snd_pcm_format_names[] = { + FORMAT(S8), + FORMAT(U8), + FORMAT(S16_LE), + FORMAT(S16_BE), + FORMAT(U16_LE), + FORMAT(U16_BE), + FORMAT(S24_LE), + FORMAT(S24_BE), + FORMAT(U24_LE), + FORMAT(U24_BE), + FORMAT(S32_LE), + FORMAT(S32_BE), + FORMAT(U32_LE), + FORMAT(U32_BE), + FORMAT(FLOAT_LE), + FORMAT(FLOAT_BE), + FORMAT(FLOAT64_LE), + FORMAT(FLOAT64_BE), + FORMAT(IEC958_SUBFRAME_LE), + FORMAT(IEC958_SUBFRAME_BE), + FORMAT(MU_LAW), + FORMAT(A_LAW), + FORMAT(IMA_ADPCM), + FORMAT(MPEG), + FORMAT(GSM), + FORMAT(SPECIAL), + FORMAT(S24_3LE), + FORMAT(S24_3BE), + FORMAT(U24_3LE), + FORMAT(U24_3BE), + FORMAT(S20_3LE), + FORMAT(S20_3BE), + FORMAT(U20_3LE), + FORMAT(U20_3BE), + FORMAT(S18_3LE), + FORMAT(S18_3BE), + FORMAT(U18_3LE), + FORMAT(U18_3BE), + FORMAT(G723_24), + FORMAT(G723_24_1B), + FORMAT(G723_40), + FORMAT(G723_40_1B), + FORMAT(DSD_U8), + FORMAT(DSD_U16_LE), + FORMAT(DSD_U32_LE), + FORMAT(DSD_U16_BE), + FORMAT(DSD_U32_BE), +}; + +/** + * snd_pcm_format_name - Return a name string for the given PCM format + * @format: PCM format + * + * Return: the format name string + */ +const char *snd_pcm_format_name(snd_pcm_format_t format) +{ + if ((__force unsigned int)format >= ARRAY_SIZE(snd_pcm_format_names)) + return "Unknown"; + return snd_pcm_format_names[(__force unsigned int)format]; +} +EXPORT_SYMBOL_GPL(snd_pcm_format_name); + +#ifdef CONFIG_SND_VERBOSE_PROCFS + +#define STATE(v) [SNDRV_PCM_STATE_##v] = #v +#define STREAM(v) [SNDRV_PCM_STREAM_##v] = #v +#define READY(v) [SNDRV_PCM_READY_##v] = #v +#define XRUN(v) [SNDRV_PCM_XRUN_##v] = #v +#define SILENCE(v) [SNDRV_PCM_SILENCE_##v] = #v +#define TSTAMP(v) [SNDRV_PCM_TSTAMP_##v] = #v +#define ACCESS(v) [SNDRV_PCM_ACCESS_##v] = #v +#define START(v) [SNDRV_PCM_START_##v] = #v +#define SUBFORMAT(v) [SNDRV_PCM_SUBFORMAT_##v] = #v + +static const char * const snd_pcm_stream_names[] = { + STREAM(PLAYBACK), + STREAM(CAPTURE), +}; + +static const char * const snd_pcm_state_names[] = { + STATE(OPEN), + STATE(SETUP), + STATE(PREPARED), + STATE(RUNNING), + STATE(XRUN), + STATE(DRAINING), + STATE(PAUSED), + STATE(SUSPENDED), + STATE(DISCONNECTED), +}; + +static const char * const snd_pcm_access_names[] = { + ACCESS(MMAP_INTERLEAVED), + ACCESS(MMAP_NONINTERLEAVED), + ACCESS(MMAP_COMPLEX), + ACCESS(RW_INTERLEAVED), + ACCESS(RW_NONINTERLEAVED), +}; + +static const char * const snd_pcm_subformat_names[] = { + SUBFORMAT(STD), +}; + +static const char * const snd_pcm_tstamp_mode_names[] = { + TSTAMP(NONE), + TSTAMP(ENABLE), +}; + +static const char *snd_pcm_stream_name(int stream) +{ + return snd_pcm_stream_names[stream]; +} + +static const char *snd_pcm_access_name(snd_pcm_access_t access) +{ + return snd_pcm_access_names[(__force int)access]; +} + +static const char *snd_pcm_subformat_name(snd_pcm_subformat_t subformat) +{ + return snd_pcm_subformat_names[(__force int)subformat]; +} + +static const char *snd_pcm_tstamp_mode_name(int mode) +{ + return snd_pcm_tstamp_mode_names[mode]; +} + +static const char *snd_pcm_state_name(snd_pcm_state_t state) +{ + return snd_pcm_state_names[(__force int)state]; +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#include <linux/soundcard.h> + +static const char *snd_pcm_oss_format_name(int format) +{ + switch (format) { + case AFMT_MU_LAW: + return "MU_LAW"; + case AFMT_A_LAW: + return "A_LAW"; + case AFMT_IMA_ADPCM: + return "IMA_ADPCM"; + case AFMT_U8: + return "U8"; + case AFMT_S16_LE: + return "S16_LE"; + case AFMT_S16_BE: + return "S16_BE"; + case AFMT_S8: + return "S8"; + case AFMT_U16_LE: + return "U16_LE"; + case AFMT_U16_BE: + return "U16_BE"; + case AFMT_MPEG: + return "MPEG"; + default: + return "unknown"; + } +} +#endif + +static void snd_pcm_proc_info_read(struct snd_pcm_substream *substream, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_info *info; + int err; + + if (! substream) + return; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return; + + err = snd_pcm_info(substream, info); + if (err < 0) { + snd_iprintf(buffer, "error %d\n", err); + kfree(info); + return; + } + snd_iprintf(buffer, "card: %d\n", info->card); + snd_iprintf(buffer, "device: %d\n", info->device); + snd_iprintf(buffer, "subdevice: %d\n", info->subdevice); + snd_iprintf(buffer, "stream: %s\n", snd_pcm_stream_name(info->stream)); + snd_iprintf(buffer, "id: %s\n", info->id); + snd_iprintf(buffer, "name: %s\n", info->name); + snd_iprintf(buffer, "subname: %s\n", info->subname); + snd_iprintf(buffer, "class: %d\n", info->dev_class); + snd_iprintf(buffer, "subclass: %d\n", info->dev_subclass); + snd_iprintf(buffer, "subdevices_count: %d\n", info->subdevices_count); + snd_iprintf(buffer, "subdevices_avail: %d\n", info->subdevices_avail); + kfree(info); +} + +static void snd_pcm_stream_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_pcm_proc_info_read(((struct snd_pcm_str *)entry->private_data)->substream, + buffer); +} + +static void snd_pcm_substream_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + snd_pcm_proc_info_read(entry->private_data, buffer); +} + +static void snd_pcm_substream_proc_hw_params_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct snd_pcm_runtime *runtime; + + mutex_lock(&substream->pcm->open_mutex); + runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + goto unlock; + } + if (runtime->state == SNDRV_PCM_STATE_OPEN) { + snd_iprintf(buffer, "no setup\n"); + goto unlock; + } + snd_iprintf(buffer, "access: %s\n", snd_pcm_access_name(runtime->access)); + snd_iprintf(buffer, "format: %s\n", snd_pcm_format_name(runtime->format)); + snd_iprintf(buffer, "subformat: %s\n", snd_pcm_subformat_name(runtime->subformat)); + snd_iprintf(buffer, "channels: %u\n", runtime->channels); + snd_iprintf(buffer, "rate: %u (%u/%u)\n", runtime->rate, runtime->rate_num, runtime->rate_den); + snd_iprintf(buffer, "period_size: %lu\n", runtime->period_size); + snd_iprintf(buffer, "buffer_size: %lu\n", runtime->buffer_size); +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + if (substream->oss.oss) { + snd_iprintf(buffer, "OSS format: %s\n", snd_pcm_oss_format_name(runtime->oss.format)); + snd_iprintf(buffer, "OSS channels: %u\n", runtime->oss.channels); + snd_iprintf(buffer, "OSS rate: %u\n", runtime->oss.rate); + snd_iprintf(buffer, "OSS period bytes: %lu\n", (unsigned long)runtime->oss.period_bytes); + snd_iprintf(buffer, "OSS periods: %u\n", runtime->oss.periods); + snd_iprintf(buffer, "OSS period frames: %lu\n", (unsigned long)runtime->oss.period_frames); + } +#endif + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +static void snd_pcm_substream_proc_sw_params_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct snd_pcm_runtime *runtime; + + mutex_lock(&substream->pcm->open_mutex); + runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + goto unlock; + } + if (runtime->state == SNDRV_PCM_STATE_OPEN) { + snd_iprintf(buffer, "no setup\n"); + goto unlock; + } + snd_iprintf(buffer, "tstamp_mode: %s\n", snd_pcm_tstamp_mode_name(runtime->tstamp_mode)); + snd_iprintf(buffer, "period_step: %u\n", runtime->period_step); + snd_iprintf(buffer, "avail_min: %lu\n", runtime->control->avail_min); + snd_iprintf(buffer, "start_threshold: %lu\n", runtime->start_threshold); + snd_iprintf(buffer, "stop_threshold: %lu\n", runtime->stop_threshold); + snd_iprintf(buffer, "silence_threshold: %lu\n", runtime->silence_threshold); + snd_iprintf(buffer, "silence_size: %lu\n", runtime->silence_size); + snd_iprintf(buffer, "boundary: %lu\n", runtime->boundary); + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +static void snd_pcm_substream_proc_status_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct snd_pcm_runtime *runtime; + struct snd_pcm_status64 status; + int err; + + mutex_lock(&substream->pcm->open_mutex); + runtime = substream->runtime; + if (!runtime) { + snd_iprintf(buffer, "closed\n"); + goto unlock; + } + memset(&status, 0, sizeof(status)); + err = snd_pcm_status64(substream, &status); + if (err < 0) { + snd_iprintf(buffer, "error %d\n", err); + goto unlock; + } + snd_iprintf(buffer, "state: %s\n", snd_pcm_state_name(status.state)); + snd_iprintf(buffer, "owner_pid : %d\n", pid_vnr(substream->pid)); + snd_iprintf(buffer, "trigger_time: %lld.%09lld\n", + status.trigger_tstamp_sec, status.trigger_tstamp_nsec); + snd_iprintf(buffer, "tstamp : %lld.%09lld\n", + status.tstamp_sec, status.tstamp_nsec); + snd_iprintf(buffer, "delay : %ld\n", status.delay); + snd_iprintf(buffer, "avail : %ld\n", status.avail); + snd_iprintf(buffer, "avail_max : %ld\n", status.avail_max); + snd_iprintf(buffer, "-----\n"); + snd_iprintf(buffer, "hw_ptr : %ld\n", runtime->status->hw_ptr); + snd_iprintf(buffer, "appl_ptr : %ld\n", runtime->control->appl_ptr); + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG +static void snd_pcm_xrun_injection_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + + snd_pcm_stop_xrun(substream); +} + +static void snd_pcm_xrun_debug_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + snd_iprintf(buffer, "%d\n", pstr->xrun_debug); +} + +static void snd_pcm_xrun_debug_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_str *pstr = entry->private_data; + char line[64]; + if (!snd_info_get_line(buffer, line, sizeof(line))) + pstr->xrun_debug = simple_strtoul(line, NULL, 10); +} +#endif + +static int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) +{ + struct snd_pcm *pcm = pstr->pcm; + struct snd_info_entry *entry; + char name[16]; + + sprintf(name, "pcm%i%c", pcm->device, + pstr->stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); + entry = snd_info_create_card_entry(pcm->card, name, + pcm->card->proc_root); + if (!entry) + return -ENOMEM; + entry->mode = S_IFDIR | 0555; + pstr->proc_root = entry; + entry = snd_info_create_card_entry(pcm->card, "info", pstr->proc_root); + if (entry) + snd_info_set_text_ops(entry, pstr, snd_pcm_stream_proc_info_read); +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + entry = snd_info_create_card_entry(pcm->card, "xrun_debug", + pstr->proc_root); + if (entry) { + snd_info_set_text_ops(entry, pstr, snd_pcm_xrun_debug_read); + entry->c.text.write = snd_pcm_xrun_debug_write; + entry->mode |= 0200; + } +#endif + return 0; +} + +static int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) +{ + snd_info_free_entry(pstr->proc_root); + pstr->proc_root = NULL; + return 0; +} + +static struct snd_info_entry * +create_substream_info_entry(struct snd_pcm_substream *substream, + const char *name, + void (*read)(struct snd_info_entry *, + struct snd_info_buffer *)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(substream->pcm->card, name, + substream->proc_root); + if (entry) + snd_info_set_text_ops(entry, substream, read); + return entry; +} + +static int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) +{ + struct snd_info_entry *entry; + struct snd_card *card; + char name[16]; + + card = substream->pcm->card; + + sprintf(name, "sub%i", substream->number); + entry = snd_info_create_card_entry(card, name, + substream->pstr->proc_root); + if (!entry) + return -ENOMEM; + entry->mode = S_IFDIR | 0555; + substream->proc_root = entry; + + create_substream_info_entry(substream, "info", + snd_pcm_substream_proc_info_read); + create_substream_info_entry(substream, "hw_params", + snd_pcm_substream_proc_hw_params_read); + create_substream_info_entry(substream, "sw_params", + snd_pcm_substream_proc_sw_params_read); + create_substream_info_entry(substream, "status", + snd_pcm_substream_proc_status_read); + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + entry = create_substream_info_entry(substream, "xrun_injection", NULL); + if (entry) { + entry->c.text.write = snd_pcm_xrun_injection_write; + entry->mode = S_IFREG | 0200; + } +#endif /* CONFIG_SND_PCM_XRUN_DEBUG */ + + return 0; +} + +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +static inline int snd_pcm_stream_proc_init(struct snd_pcm_str *pstr) { return 0; } +static inline int snd_pcm_stream_proc_done(struct snd_pcm_str *pstr) { return 0; } +static inline int snd_pcm_substream_proc_init(struct snd_pcm_substream *substream) { return 0; } +#endif /* CONFIG_SND_VERBOSE_PROCFS */ + +static const struct attribute_group *pcm_dev_attr_groups[]; + +/* + * PM callbacks: we need to deal only with suspend here, as the resume is + * triggered either from user-space or the driver's resume callback + */ +#ifdef CONFIG_PM_SLEEP +static int do_pcm_suspend(struct device *dev) +{ + struct snd_pcm_str *pstr = dev_get_drvdata(dev); + + if (!pstr->pcm->no_device_suspend) + snd_pcm_suspend_all(pstr->pcm); + return 0; +} +#endif + +static const struct dev_pm_ops pcm_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(do_pcm_suspend, NULL) +}; + +/* device type for PCM -- basically only for passing PM callbacks */ +static const struct device_type pcm_dev_type = { + .name = "pcm", + .pm = &pcm_dev_pm_ops, +}; + +/** + * snd_pcm_new_stream - create a new PCM stream + * @pcm: the pcm instance + * @stream: the stream direction, SNDRV_PCM_STREAM_XXX + * @substream_count: the number of substreams + * + * Creates a new stream for the pcm. + * The corresponding stream on the pcm must have been empty before + * calling this, i.e. zero must be given to the argument of + * snd_pcm_new(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count) +{ + int idx, err; + struct snd_pcm_str *pstr = &pcm->streams[stream]; + struct snd_pcm_substream *substream, *prev; + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + mutex_init(&pstr->oss.setup_mutex); +#endif + pstr->stream = stream; + pstr->pcm = pcm; + pstr->substream_count = substream_count; + if (!substream_count) + return 0; + + err = snd_device_alloc(&pstr->dev, pcm->card); + if (err < 0) + return err; + dev_set_name(pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device, + stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c'); + pstr->dev->groups = pcm_dev_attr_groups; + pstr->dev->type = &pcm_dev_type; + dev_set_drvdata(pstr->dev, pstr); + + if (!pcm->internal) { + err = snd_pcm_stream_proc_init(pstr); + if (err < 0) { + pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n"); + return err; + } + } + prev = NULL; + for (idx = 0, prev = NULL; idx < substream_count; idx++) { + substream = kzalloc(sizeof(*substream), GFP_KERNEL); + if (!substream) + return -ENOMEM; + substream->pcm = pcm; + substream->pstr = pstr; + substream->number = idx; + substream->stream = stream; + sprintf(substream->name, "subdevice #%i", idx); + substream->buffer_bytes_max = UINT_MAX; + if (prev == NULL) + pstr->substream = substream; + else + prev->next = substream; + + if (!pcm->internal) { + err = snd_pcm_substream_proc_init(substream); + if (err < 0) { + pcm_err(pcm, + "Error in snd_pcm_stream_proc_init\n"); + if (prev == NULL) + pstr->substream = NULL; + else + prev->next = NULL; + kfree(substream); + return err; + } + } + substream->group = &substream->self_group; + snd_pcm_group_init(&substream->self_group); + list_add_tail(&substream->link_list, &substream->self_group.substreams); + atomic_set(&substream->mmap_count, 0); + prev = substream; + } + return 0; +} +EXPORT_SYMBOL(snd_pcm_new_stream); + +static int _snd_pcm_new(struct snd_card *card, const char *id, int device, + int playback_count, int capture_count, bool internal, + struct snd_pcm **rpcm) +{ + struct snd_pcm *pcm; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_pcm_dev_free, + .dev_register = snd_pcm_dev_register, + .dev_disconnect = snd_pcm_dev_disconnect, + }; + static const struct snd_device_ops internal_ops = { + .dev_free = snd_pcm_dev_free, + }; + + if (snd_BUG_ON(!card)) + return -ENXIO; + if (rpcm) + *rpcm = NULL; + pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); + if (!pcm) + return -ENOMEM; + pcm->card = card; + pcm->device = device; + pcm->internal = internal; + mutex_init(&pcm->open_mutex); + init_waitqueue_head(&pcm->open_wait); + INIT_LIST_HEAD(&pcm->list); + if (id) + strscpy(pcm->id, id, sizeof(pcm->id)); + + err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, + playback_count); + if (err < 0) + goto free_pcm; + + err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count); + if (err < 0) + goto free_pcm; + + err = snd_device_new(card, SNDRV_DEV_PCM, pcm, + internal ? &internal_ops : &ops); + if (err < 0) + goto free_pcm; + + if (rpcm) + *rpcm = pcm; + return 0; + +free_pcm: + snd_pcm_free(pcm); + return err; +} + +/** + * snd_pcm_new - create a new PCM instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero based) + * @playback_count: the number of substreams for playback + * @capture_count: the number of substreams for capture + * @rpcm: the pointer to store the new pcm instance + * + * Creates a new PCM instance. + * + * The pcm operators have to be set afterwards to the new instance + * via snd_pcm_set_ops(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_new(struct snd_card *card, const char *id, int device, + int playback_count, int capture_count, struct snd_pcm **rpcm) +{ + return _snd_pcm_new(card, id, device, playback_count, capture_count, + false, rpcm); +} +EXPORT_SYMBOL(snd_pcm_new); + +/** + * snd_pcm_new_internal - create a new internal PCM instance + * @card: the card instance + * @id: the id string + * @device: the device index (zero based - shared with normal PCMs) + * @playback_count: the number of substreams for playback + * @capture_count: the number of substreams for capture + * @rpcm: the pointer to store the new pcm instance + * + * Creates a new internal PCM instance with no userspace device or procfs + * entries. This is used by ASoC Back End PCMs in order to create a PCM that + * will only be used internally by kernel drivers. i.e. it cannot be opened + * by userspace. It provides existing ASoC components drivers with a substream + * and access to any private data. + * + * The pcm operators have to be set afterwards to the new instance + * via snd_pcm_set_ops(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_new_internal(struct snd_card *card, const char *id, int device, + int playback_count, int capture_count, + struct snd_pcm **rpcm) +{ + return _snd_pcm_new(card, id, device, playback_count, capture_count, + true, rpcm); +} +EXPORT_SYMBOL(snd_pcm_new_internal); + +static void free_chmap(struct snd_pcm_str *pstr) +{ + if (pstr->chmap_kctl) { + struct snd_card *card = pstr->pcm->card; + + snd_ctl_remove(card, pstr->chmap_kctl); + pstr->chmap_kctl = NULL; + } +} + +static void snd_pcm_free_stream(struct snd_pcm_str * pstr) +{ + struct snd_pcm_substream *substream, *substream_next; +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + struct snd_pcm_oss_setup *setup, *setupn; +#endif + + /* free all proc files under the stream */ + snd_pcm_stream_proc_done(pstr); + + substream = pstr->substream; + while (substream) { + substream_next = substream->next; + snd_pcm_timer_done(substream); + kfree(substream); + substream = substream_next; + } +#if IS_ENABLED(CONFIG_SND_PCM_OSS) + for (setup = pstr->oss.setup_list; setup; setup = setupn) { + setupn = setup->next; + kfree(setup->task_name); + kfree(setup); + } +#endif + free_chmap(pstr); + if (pstr->substream_count) + put_device(pstr->dev); +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#define pcm_call_notify(pcm, call) \ + do { \ + struct snd_pcm_notify *_notify; \ + list_for_each_entry(_notify, &snd_pcm_notify_list, list) \ + _notify->call(pcm); \ + } while (0) +#else +#define pcm_call_notify(pcm, call) do {} while (0) +#endif + +static int snd_pcm_free(struct snd_pcm *pcm) +{ + if (!pcm) + return 0; + if (!pcm->internal) + pcm_call_notify(pcm, n_unregister); + if (pcm->private_free) + pcm->private_free(pcm); + snd_pcm_lib_preallocate_free_for_all(pcm); + snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]); + snd_pcm_free_stream(&pcm->streams[SNDRV_PCM_STREAM_CAPTURE]); + kfree(pcm); + return 0; +} + +static int snd_pcm_dev_free(struct snd_device *device) +{ + struct snd_pcm *pcm = device->device_data; + return snd_pcm_free(pcm); +} + +int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, + struct file *file, + struct snd_pcm_substream **rsubstream) +{ + struct snd_pcm_str * pstr; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct snd_card *card; + int prefer_subdevice; + size_t size; + + if (snd_BUG_ON(!pcm || !rsubstream)) + return -ENXIO; + if (snd_BUG_ON(stream != SNDRV_PCM_STREAM_PLAYBACK && + stream != SNDRV_PCM_STREAM_CAPTURE)) + return -EINVAL; + *rsubstream = NULL; + pstr = &pcm->streams[stream]; + if (pstr->substream == NULL || pstr->substream_count == 0) + return -ENODEV; + + card = pcm->card; + prefer_subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_PCM); + + if (pcm->info_flags & SNDRV_PCM_INFO_HALF_DUPLEX) { + int opposite = !stream; + + for (substream = pcm->streams[opposite].substream; substream; + substream = substream->next) { + if (SUBSTREAM_BUSY(substream)) + return -EAGAIN; + } + } + + if (file->f_flags & O_APPEND) { + if (prefer_subdevice < 0) { + if (pstr->substream_count > 1) + return -EINVAL; /* must be unique */ + substream = pstr->substream; + } else { + for (substream = pstr->substream; substream; + substream = substream->next) + if (substream->number == prefer_subdevice) + break; + } + if (! substream) + return -ENODEV; + if (! SUBSTREAM_BUSY(substream)) + return -EBADFD; + substream->ref_count++; + *rsubstream = substream; + return 0; + } + + for (substream = pstr->substream; substream; substream = substream->next) { + if (!SUBSTREAM_BUSY(substream) && + (prefer_subdevice == -1 || + substream->number == prefer_subdevice)) + break; + } + if (substream == NULL) + return -EAGAIN; + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (runtime == NULL) + return -ENOMEM; + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status)); + runtime->status = alloc_pages_exact(size, GFP_KERNEL); + if (runtime->status == NULL) { + kfree(runtime); + return -ENOMEM; + } + memset(runtime->status, 0, size); + + size = PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)); + runtime->control = alloc_pages_exact(size, GFP_KERNEL); + if (runtime->control == NULL) { + free_pages_exact(runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); + kfree(runtime); + return -ENOMEM; + } + memset(runtime->control, 0, size); + + init_waitqueue_head(&runtime->sleep); + init_waitqueue_head(&runtime->tsleep); + + __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_OPEN); + mutex_init(&runtime->buffer_mutex); + atomic_set(&runtime->buffer_accessing, 0); + + substream->runtime = runtime; + substream->private_data = pcm->private_data; + substream->ref_count = 1; + substream->f_flags = file->f_flags; + substream->pid = get_pid(task_pid(current)); + pstr->substream_opened++; + *rsubstream = substream; + return 0; +} + +void snd_pcm_detach_substream(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return; + runtime = substream->runtime; + if (runtime->private_free != NULL) + runtime->private_free(runtime); + free_pages_exact(runtime->status, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))); + free_pages_exact(runtime->control, + PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control))); + kfree(runtime->hw_constraints.rules); + /* Avoid concurrent access to runtime via PCM timer interface */ + if (substream->timer) { + spin_lock_irq(&substream->timer->lock); + substream->runtime = NULL; + spin_unlock_irq(&substream->timer->lock); + } else { + substream->runtime = NULL; + } + mutex_destroy(&runtime->buffer_mutex); + snd_fasync_free(runtime->fasync); + kfree(runtime); + put_pid(substream->pid); + substream->pid = NULL; + substream->pstr->substream_opened--; +} + +static ssize_t pcm_class_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_pcm_str *pstr = dev_get_drvdata(dev); + struct snd_pcm *pcm = pstr->pcm; + const char *str; + static const char *strs[SNDRV_PCM_CLASS_LAST + 1] = { + [SNDRV_PCM_CLASS_GENERIC] = "generic", + [SNDRV_PCM_CLASS_MULTI] = "multi", + [SNDRV_PCM_CLASS_MODEM] = "modem", + [SNDRV_PCM_CLASS_DIGITIZER] = "digitizer", + }; + + if (pcm->dev_class > SNDRV_PCM_CLASS_LAST) + str = "none"; + else + str = strs[pcm->dev_class]; + return sysfs_emit(buf, "%s\n", str); +} + +static DEVICE_ATTR_RO(pcm_class); +static struct attribute *pcm_dev_attrs[] = { + &dev_attr_pcm_class.attr, + NULL +}; + +static const struct attribute_group pcm_dev_attr_group = { + .attrs = pcm_dev_attrs, +}; + +static const struct attribute_group *pcm_dev_attr_groups[] = { + &pcm_dev_attr_group, + NULL +}; + +static int snd_pcm_dev_register(struct snd_device *device) +{ + int cidx, err; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm; + + if (snd_BUG_ON(!device || !device->device_data)) + return -ENXIO; + pcm = device->device_data; + + mutex_lock(®ister_mutex); + err = snd_pcm_add(pcm); + if (err) + goto unlock; + for (cidx = 0; cidx < 2; cidx++) { + int devtype = -1; + if (pcm->streams[cidx].substream == NULL) + continue; + switch (cidx) { + case SNDRV_PCM_STREAM_PLAYBACK: + devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK; + break; + case SNDRV_PCM_STREAM_CAPTURE: + devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE; + break; + } + /* register pcm */ + err = snd_register_device(devtype, pcm->card, pcm->device, + &snd_pcm_f_ops[cidx], pcm, + pcm->streams[cidx].dev); + if (err < 0) { + list_del_init(&pcm->list); + goto unlock; + } + + for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) + snd_pcm_timer_init(substream); + } + + pcm_call_notify(pcm, n_register); + + unlock: + mutex_unlock(®ister_mutex); + return err; +} + +static int snd_pcm_dev_disconnect(struct snd_device *device) +{ + struct snd_pcm *pcm = device->device_data; + struct snd_pcm_substream *substream; + int cidx; + + mutex_lock(®ister_mutex); + mutex_lock(&pcm->open_mutex); + wake_up(&pcm->open_wait); + list_del_init(&pcm->list); + + for_each_pcm_substream(pcm, cidx, substream) { + snd_pcm_stream_lock_irq(substream); + if (substream->runtime) { + if (snd_pcm_running(substream)) + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + /* to be sure, set the state unconditionally */ + __snd_pcm_set_state(substream->runtime, + SNDRV_PCM_STATE_DISCONNECTED); + wake_up(&substream->runtime->sleep); + wake_up(&substream->runtime->tsleep); + } + snd_pcm_stream_unlock_irq(substream); + } + + for_each_pcm_substream(pcm, cidx, substream) + snd_pcm_sync_stop(substream, false); + + pcm_call_notify(pcm, n_disconnect); + for (cidx = 0; cidx < 2; cidx++) { + if (pcm->streams[cidx].dev) + snd_unregister_device(pcm->streams[cidx].dev); + free_chmap(&pcm->streams[cidx]); + } + mutex_unlock(&pcm->open_mutex); + mutex_unlock(®ister_mutex); + return 0; +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +/** + * snd_pcm_notify - Add/remove the notify list + * @notify: PCM notify list + * @nfree: 0 = register, 1 = unregister + * + * This adds the given notifier to the global list so that the callback is + * called for each registered PCM devices. This exists only for PCM OSS + * emulation, so far. + * + * Return: zero if successful, or a negative error code + */ +int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree) +{ + struct snd_pcm *pcm; + + if (snd_BUG_ON(!notify || + !notify->n_register || + !notify->n_unregister || + !notify->n_disconnect)) + return -EINVAL; + mutex_lock(®ister_mutex); + if (nfree) { + list_del(¬ify->list); + list_for_each_entry(pcm, &snd_pcm_devices, list) + notify->n_unregister(pcm); + } else { + list_add_tail(¬ify->list, &snd_pcm_notify_list); + list_for_each_entry(pcm, &snd_pcm_devices, list) + notify->n_register(pcm); + } + mutex_unlock(®ister_mutex); + return 0; +} +EXPORT_SYMBOL(snd_pcm_notify); +#endif /* CONFIG_SND_PCM_OSS */ + +#ifdef CONFIG_SND_PROC_FS +/* + * Info interface + */ + +static void snd_pcm_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm *pcm; + + mutex_lock(®ister_mutex); + list_for_each_entry(pcm, &snd_pcm_devices, list) { + snd_iprintf(buffer, "%02i-%02i: %s : %s", + pcm->card->number, pcm->device, pcm->id, pcm->name); + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) + snd_iprintf(buffer, " : playback %i", + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream_count); + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) + snd_iprintf(buffer, " : capture %i", + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count); + snd_iprintf(buffer, "\n"); + } + mutex_unlock(®ister_mutex); +} + +static struct snd_info_entry *snd_pcm_proc_entry; + +static void snd_pcm_proc_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "pcm", NULL); + if (entry) { + snd_info_set_text_ops(entry, NULL, snd_pcm_proc_read); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_pcm_proc_entry = entry; +} + +static void snd_pcm_proc_done(void) +{ + snd_info_free_entry(snd_pcm_proc_entry); +} + +#else /* !CONFIG_SND_PROC_FS */ +#define snd_pcm_proc_init() +#define snd_pcm_proc_done() +#endif /* CONFIG_SND_PROC_FS */ + + +/* + * ENTRY functions + */ + +static int __init alsa_pcm_init(void) +{ + snd_ctl_register_ioctl(snd_pcm_control_ioctl); + snd_ctl_register_ioctl_compat(snd_pcm_control_ioctl); + snd_pcm_proc_init(); + return 0; +} + +static void __exit alsa_pcm_exit(void) +{ + snd_ctl_unregister_ioctl(snd_pcm_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_pcm_control_ioctl); + snd_pcm_proc_done(); +} + +module_init(alsa_pcm_init) +module_exit(alsa_pcm_exit) diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c new file mode 100644 index 0000000000..c96483091f --- /dev/null +++ b/sound/core/pcm_compat.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 32bit -> 64bit ioctl wrapper for PCM API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +/* This file included from pcm_native.c */ + +#include <linux/compat.h> +#include <linux/slab.h> + +static int snd_pcm_ioctl_delay_compat(struct snd_pcm_substream *substream, + s32 __user *src) +{ + snd_pcm_sframes_t delay; + int err; + + err = snd_pcm_delay(substream, &delay); + if (err) + return err; + if (put_user(delay, src)) + return -EFAULT; + return 0; +} + +static int snd_pcm_ioctl_rewind_compat(struct snd_pcm_substream *substream, + u32 __user *src) +{ + snd_pcm_uframes_t frames; + int err; + + if (get_user(frames, src)) + return -EFAULT; + err = snd_pcm_rewind(substream, frames); + if (put_user(err, src)) + return -EFAULT; + return err < 0 ? err : 0; +} + +static int snd_pcm_ioctl_forward_compat(struct snd_pcm_substream *substream, + u32 __user *src) +{ + snd_pcm_uframes_t frames; + int err; + + if (get_user(frames, src)) + return -EFAULT; + err = snd_pcm_forward(substream, frames); + if (put_user(err, src)) + return -EFAULT; + return err < 0 ? err : 0; +} + +struct snd_pcm_hw_params32 { + u32 flags; + struct snd_mask masks[SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1]; /* this must be identical */ + struct snd_mask mres[5]; /* reserved masks */ + struct snd_interval intervals[SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1]; + struct snd_interval ires[9]; /* reserved intervals */ + u32 rmask; + u32 cmask; + u32 info; + u32 msbits; + u32 rate_num; + u32 rate_den; + u32 fifo_size; + unsigned char reserved[64]; +}; + +struct snd_pcm_sw_params32 { + s32 tstamp_mode; + u32 period_step; + u32 sleep_min; + u32 avail_min; + u32 xfer_align; + u32 start_threshold; + u32 stop_threshold; + u32 silence_threshold; + u32 silence_size; + u32 boundary; + u32 proto; + u32 tstamp_type; + unsigned char reserved[56]; +}; + +static int snd_pcm_ioctl_sw_params_compat(struct snd_pcm_substream *substream, + struct snd_pcm_sw_params32 __user *src) +{ + struct snd_pcm_sw_params params; + snd_pcm_uframes_t boundary; + int err; + + memset(¶ms, 0, sizeof(params)); + if (get_user(params.tstamp_mode, &src->tstamp_mode) || + get_user(params.period_step, &src->period_step) || + get_user(params.sleep_min, &src->sleep_min) || + get_user(params.avail_min, &src->avail_min) || + get_user(params.xfer_align, &src->xfer_align) || + get_user(params.start_threshold, &src->start_threshold) || + get_user(params.stop_threshold, &src->stop_threshold) || + get_user(params.silence_threshold, &src->silence_threshold) || + get_user(params.silence_size, &src->silence_size) || + get_user(params.tstamp_type, &src->tstamp_type) || + get_user(params.proto, &src->proto)) + return -EFAULT; + /* + * Check silent_size parameter. Since we have 64bit boundary, + * silence_size must be compared with the 32bit boundary. + */ + boundary = recalculate_boundary(substream->runtime); + if (boundary && params.silence_size >= boundary) + params.silence_size = substream->runtime->boundary; + err = snd_pcm_sw_params(substream, ¶ms); + if (err < 0) + return err; + if (boundary && put_user(boundary, &src->boundary)) + return -EFAULT; + return err; +} + +struct snd_pcm_channel_info32 { + u32 channel; + u32 offset; + u32 first; + u32 step; +}; + +static int snd_pcm_ioctl_channel_info_compat(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info32 __user *src) +{ + struct snd_pcm_channel_info info; + int err; + + if (get_user(info.channel, &src->channel) || + get_user(info.offset, &src->offset) || + get_user(info.first, &src->first) || + get_user(info.step, &src->step)) + return -EFAULT; + err = snd_pcm_channel_info(substream, &info); + if (err < 0) + return err; + if (put_user(info.channel, &src->channel) || + put_user(info.offset, &src->offset) || + put_user(info.first, &src->first) || + put_user(info.step, &src->step)) + return -EFAULT; + return err; +} + +#ifdef CONFIG_X86_X32_ABI +/* X32 ABI has the same struct as x86-64 for snd_pcm_channel_info */ +static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info __user *src); +#define snd_pcm_ioctl_channel_info_x32(s, p) \ + snd_pcm_channel_info_user(s, p) +#endif /* CONFIG_X86_X32_ABI */ + +struct compat_snd_pcm_status64 { + snd_pcm_state_t state; + u8 rsvd[4]; /* alignment */ + s64 trigger_tstamp_sec; + s64 trigger_tstamp_nsec; + s64 tstamp_sec; + s64 tstamp_nsec; + u32 appl_ptr; + u32 hw_ptr; + s32 delay; + u32 avail; + u32 avail_max; + u32 overrange; + snd_pcm_state_t suspended_state; + u32 audio_tstamp_data; + s64 audio_tstamp_sec; + s64 audio_tstamp_nsec; + s64 driver_tstamp_sec; + s64 driver_tstamp_nsec; + u32 audio_tstamp_accuracy; + unsigned char reserved[52-4*sizeof(s64)]; +} __packed; + +static int snd_pcm_status_user_compat64(struct snd_pcm_substream *substream, + struct compat_snd_pcm_status64 __user *src, + bool ext) +{ + struct snd_pcm_status64 status; + struct compat_snd_pcm_status64 compat_status64; + int err; + + memset(&status, 0, sizeof(status)); + memset(&compat_status64, 0, sizeof(compat_status64)); + /* + * with extension, parameters are read/write, + * get audio_tstamp_data from user, + * ignore rest of status structure + */ + if (ext && get_user(status.audio_tstamp_data, + (u32 __user *)(&src->audio_tstamp_data))) + return -EFAULT; + err = snd_pcm_status64(substream, &status); + if (err < 0) + return err; + + if (clear_user(src, sizeof(*src))) + return -EFAULT; + + compat_status64 = (struct compat_snd_pcm_status64) { + .state = status.state, + .trigger_tstamp_sec = status.trigger_tstamp_sec, + .trigger_tstamp_nsec = status.trigger_tstamp_nsec, + .tstamp_sec = status.tstamp_sec, + .tstamp_nsec = status.tstamp_nsec, + .appl_ptr = status.appl_ptr, + .hw_ptr = status.hw_ptr, + .delay = status.delay, + .avail = status.avail, + .avail_max = status.avail_max, + .overrange = status.overrange, + .suspended_state = status.suspended_state, + .audio_tstamp_data = status.audio_tstamp_data, + .audio_tstamp_sec = status.audio_tstamp_sec, + .audio_tstamp_nsec = status.audio_tstamp_nsec, + .driver_tstamp_sec = status.audio_tstamp_sec, + .driver_tstamp_nsec = status.audio_tstamp_nsec, + .audio_tstamp_accuracy = status.audio_tstamp_accuracy, + }; + + if (copy_to_user(src, &compat_status64, sizeof(compat_status64))) + return -EFAULT; + + return err; +} + +/* both for HW_PARAMS and HW_REFINE */ +static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, + int refine, + struct snd_pcm_hw_params32 __user *data32) +{ + struct snd_pcm_hw_params *data; + struct snd_pcm_runtime *runtime; + int err; + + runtime = substream->runtime; + if (!runtime) + return -ENOTTY; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* only fifo_size (RO from userspace) is different, so just copy all */ + if (copy_from_user(data, data32, sizeof(*data32))) { + err = -EFAULT; + goto error; + } + + if (refine) { + err = snd_pcm_hw_refine(substream, data); + if (err < 0) + goto error; + err = fixup_unreferenced_params(substream, data); + } else { + err = snd_pcm_hw_params(substream, data); + } + if (err < 0) + goto error; + if (copy_to_user(data32, data, sizeof(*data32)) || + put_user(data->fifo_size, &data32->fifo_size)) { + err = -EFAULT; + goto error; + } + + if (! refine) { + unsigned int new_boundary = recalculate_boundary(runtime); + if (new_boundary) + runtime->boundary = new_boundary; + } + error: + kfree(data); + return err; +} + + +/* + */ +struct snd_xferi32 { + s32 result; + u32 buf; + u32 frames; +}; + +static int snd_pcm_ioctl_xferi_compat(struct snd_pcm_substream *substream, + int dir, struct snd_xferi32 __user *data32) +{ + compat_caddr_t buf; + u32 frames; + int err; + + if (! substream->runtime) + return -ENOTTY; + if (substream->stream != dir) + return -EINVAL; + if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + if (get_user(buf, &data32->buf) || + get_user(frames, &data32->frames)) + return -EFAULT; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_lib_write(substream, compat_ptr(buf), frames); + else + err = snd_pcm_lib_read(substream, compat_ptr(buf), frames); + if (err < 0) + return err; + /* copy the result */ + if (put_user(err, &data32->result)) + return -EFAULT; + return 0; +} + + +/* snd_xfern needs remapping of bufs */ +struct snd_xfern32 { + s32 result; + u32 bufs; /* this is void **; */ + u32 frames; +}; + +/* + * xfern ioctl nees to copy (up to) 128 pointers on stack. + * although we may pass the copied pointers through f_op->ioctl, but the ioctl + * handler there expands again the same 128 pointers on stack, so it is better + * to handle the function (calling pcm_readv/writev) directly in this handler. + */ +static int snd_pcm_ioctl_xfern_compat(struct snd_pcm_substream *substream, + int dir, struct snd_xfern32 __user *data32) +{ + compat_caddr_t buf; + compat_caddr_t __user *bufptr; + u32 frames; + void __user **bufs; + int err, ch, i; + + if (! substream->runtime) + return -ENOTTY; + if (substream->stream != dir) + return -EINVAL; + if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + ch = substream->runtime->channels; + if (ch > 128) + return -EINVAL; + if (get_user(buf, &data32->bufs) || + get_user(frames, &data32->frames)) + return -EFAULT; + bufptr = compat_ptr(buf); + bufs = kmalloc_array(ch, sizeof(void __user *), GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < ch; i++) { + u32 ptr; + if (get_user(ptr, bufptr)) { + kfree(bufs); + return -EFAULT; + } + bufs[i] = compat_ptr(ptr); + bufptr++; + } + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_pcm_lib_writev(substream, bufs, frames); + else + err = snd_pcm_lib_readv(substream, bufs, frames); + if (err >= 0) { + if (put_user(err, &data32->result)) + err = -EFAULT; + } + kfree(bufs); + return err; +} + +#ifdef CONFIG_X86_X32_ABI +/* X32 ABI has 64bit timespec and 64bit alignment */ +struct snd_pcm_mmap_status_x32 { + snd_pcm_state_t state; + s32 pad1; + u32 hw_ptr; + u32 pad2; /* alignment */ + s64 tstamp_sec; + s64 tstamp_nsec; + snd_pcm_state_t suspended_state; + s32 pad3; + s64 audio_tstamp_sec; + s64 audio_tstamp_nsec; +} __packed; + +struct snd_pcm_mmap_control_x32 { + u32 appl_ptr; + u32 avail_min; +}; + +struct snd_pcm_sync_ptr_x32 { + u32 flags; + u32 rsvd; /* alignment */ + union { + struct snd_pcm_mmap_status_x32 status; + unsigned char reserved[64]; + } s; + union { + struct snd_pcm_mmap_control_x32 control; + unsigned char reserved[64]; + } c; +} __packed; + +static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream, + struct snd_pcm_sync_ptr_x32 __user *src) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + volatile struct snd_pcm_mmap_status *status; + volatile struct snd_pcm_mmap_control *control; + u32 sflags; + struct snd_pcm_mmap_control scontrol; + struct snd_pcm_mmap_status sstatus; + snd_pcm_uframes_t boundary; + int err; + + if (snd_BUG_ON(!runtime)) + return -EINVAL; + + if (get_user(sflags, &src->flags) || + get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + get_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + status = runtime->status; + control = runtime->control; + boundary = recalculate_boundary(runtime); + if (!boundary) + boundary = 0x7fffffff; + snd_pcm_stream_lock_irq(substream); + /* FIXME: we should consider the boundary for the sync from app */ + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + control->appl_ptr = scontrol.appl_ptr; + else + scontrol.appl_ptr = control->appl_ptr % boundary; + if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = scontrol.avail_min; + else + scontrol.avail_min = control->avail_min; + sstatus.state = status->state; + sstatus.hw_ptr = status->hw_ptr % boundary; + sstatus.tstamp = status->tstamp; + sstatus.suspended_state = status->suspended_state; + sstatus.audio_tstamp = status->audio_tstamp; + snd_pcm_stream_unlock_irq(substream); + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + if (put_user(sstatus.state, &src->s.status.state) || + put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || + put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) || + put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp_nsec) || + put_user(sstatus.suspended_state, &src->s.status.suspended_state) || + put_user(sstatus.audio_tstamp.tv_sec, &src->s.status.audio_tstamp_sec) || + put_user(sstatus.audio_tstamp.tv_nsec, &src->s.status.audio_tstamp_nsec) || + put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + put_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + + return 0; +} +#endif /* CONFIG_X86_X32_ABI */ + +#ifdef __BIG_ENDIAN +typedef char __pad_before_u32[4]; +typedef char __pad_after_u32[0]; +#else +typedef char __pad_before_u32[0]; +typedef char __pad_after_u32[4]; +#endif + +/* PCM 2.0.15 API definition had a bug in mmap control; it puts the avail_min + * at the wrong offset due to a typo in padding type. + * The bug hits only 32bit. + * A workaround for incorrect read/write is needed only in 32bit compat mode. + */ +struct __snd_pcm_mmap_control64_buggy { + __pad_before_u32 __pad1; + __u32 appl_ptr; + __pad_before_u32 __pad2; /* SiC! here is the bug */ + __pad_before_u32 __pad3; + __u32 avail_min; + __pad_after_uframe __pad4; +}; + +static int snd_pcm_ioctl_sync_ptr_buggy(struct snd_pcm_substream *substream, + struct snd_pcm_sync_ptr __user *_sync_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_sync_ptr sync_ptr; + struct __snd_pcm_mmap_control64_buggy *sync_cp; + volatile struct snd_pcm_mmap_status *status; + volatile struct snd_pcm_mmap_control *control; + int err; + + memset(&sync_ptr, 0, sizeof(sync_ptr)); + sync_cp = (struct __snd_pcm_mmap_control64_buggy *)&sync_ptr.c.control; + if (get_user(sync_ptr.flags, (unsigned __user *)&(_sync_ptr->flags))) + return -EFAULT; + if (copy_from_user(sync_cp, &(_sync_ptr->c.control), sizeof(*sync_cp))) + return -EFAULT; + status = runtime->status; + control = runtime->control; + if (sync_ptr.flags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + snd_pcm_stream_lock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) { + err = pcm_lib_apply_appl_ptr(substream, sync_cp->appl_ptr); + if (err < 0) { + snd_pcm_stream_unlock_irq(substream); + return err; + } + } else { + sync_cp->appl_ptr = control->appl_ptr; + } + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = sync_cp->avail_min; + else + sync_cp->avail_min = control->avail_min; + sync_ptr.s.status.state = status->state; + sync_ptr.s.status.hw_ptr = status->hw_ptr; + sync_ptr.s.status.tstamp = status->tstamp; + sync_ptr.s.status.suspended_state = status->suspended_state; + sync_ptr.s.status.audio_tstamp = status->audio_tstamp; + snd_pcm_stream_unlock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr))) + return -EFAULT; + return 0; +} + +/* + */ +enum { + SNDRV_PCM_IOCTL_HW_REFINE32 = _IOWR('A', 0x10, struct snd_pcm_hw_params32), + SNDRV_PCM_IOCTL_HW_PARAMS32 = _IOWR('A', 0x11, struct snd_pcm_hw_params32), + SNDRV_PCM_IOCTL_SW_PARAMS32 = _IOWR('A', 0x13, struct snd_pcm_sw_params32), + SNDRV_PCM_IOCTL_STATUS_COMPAT32 = _IOR('A', 0x20, struct snd_pcm_status32), + SNDRV_PCM_IOCTL_STATUS_EXT_COMPAT32 = _IOWR('A', 0x24, struct snd_pcm_status32), + SNDRV_PCM_IOCTL_DELAY32 = _IOR('A', 0x21, s32), + SNDRV_PCM_IOCTL_CHANNEL_INFO32 = _IOR('A', 0x32, struct snd_pcm_channel_info32), + SNDRV_PCM_IOCTL_REWIND32 = _IOW('A', 0x46, u32), + SNDRV_PCM_IOCTL_FORWARD32 = _IOW('A', 0x49, u32), + SNDRV_PCM_IOCTL_WRITEI_FRAMES32 = _IOW('A', 0x50, struct snd_xferi32), + SNDRV_PCM_IOCTL_READI_FRAMES32 = _IOR('A', 0x51, struct snd_xferi32), + SNDRV_PCM_IOCTL_WRITEN_FRAMES32 = _IOW('A', 0x52, struct snd_xfern32), + SNDRV_PCM_IOCTL_READN_FRAMES32 = _IOR('A', 0x53, struct snd_xfern32), + SNDRV_PCM_IOCTL_STATUS_COMPAT64 = _IOR('A', 0x20, struct compat_snd_pcm_status64), + SNDRV_PCM_IOCTL_STATUS_EXT_COMPAT64 = _IOWR('A', 0x24, struct compat_snd_pcm_status64), +#ifdef CONFIG_X86_X32_ABI + SNDRV_PCM_IOCTL_CHANNEL_INFO_X32 = _IOR('A', 0x32, struct snd_pcm_channel_info), + SNDRV_PCM_IOCTL_SYNC_PTR_X32 = _IOWR('A', 0x23, struct snd_pcm_sync_ptr_x32), +#endif /* CONFIG_X86_X32_ABI */ +}; + +static long snd_pcm_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + void __user *argp = compat_ptr(arg); + + pcm_file = file->private_data; + if (! pcm_file) + return -ENOTTY; + substream = pcm_file->substream; + if (! substream) + return -ENOTTY; + + /* + * When PCM is used on 32bit mode, we need to disable + * mmap of the old PCM status/control records because + * of the size incompatibility. + */ + pcm_file->no_compat_mmap = 1; + + switch (cmd) { + case SNDRV_PCM_IOCTL_PVERSION: + case SNDRV_PCM_IOCTL_INFO: + case SNDRV_PCM_IOCTL_TSTAMP: + case SNDRV_PCM_IOCTL_TTSTAMP: + case SNDRV_PCM_IOCTL_USER_PVERSION: + case SNDRV_PCM_IOCTL_HWSYNC: + case SNDRV_PCM_IOCTL_PREPARE: + case SNDRV_PCM_IOCTL_RESET: + case SNDRV_PCM_IOCTL_START: + case SNDRV_PCM_IOCTL_DROP: + case SNDRV_PCM_IOCTL_DRAIN: + case SNDRV_PCM_IOCTL_PAUSE: + case SNDRV_PCM_IOCTL_HW_FREE: + case SNDRV_PCM_IOCTL_RESUME: + case SNDRV_PCM_IOCTL_XRUN: + case SNDRV_PCM_IOCTL_LINK: + case SNDRV_PCM_IOCTL_UNLINK: + case __SNDRV_PCM_IOCTL_SYNC_PTR32: + return snd_pcm_common_ioctl(file, substream, cmd, argp); + case __SNDRV_PCM_IOCTL_SYNC_PTR64: +#ifdef CONFIG_X86_X32_ABI + if (in_x32_syscall()) + return snd_pcm_ioctl_sync_ptr_x32(substream, argp); +#endif /* CONFIG_X86_X32_ABI */ + return snd_pcm_ioctl_sync_ptr_buggy(substream, argp); + case SNDRV_PCM_IOCTL_HW_REFINE32: + return snd_pcm_ioctl_hw_params_compat(substream, 1, argp); + case SNDRV_PCM_IOCTL_HW_PARAMS32: + return snd_pcm_ioctl_hw_params_compat(substream, 0, argp); + case SNDRV_PCM_IOCTL_SW_PARAMS32: + return snd_pcm_ioctl_sw_params_compat(substream, argp); + case SNDRV_PCM_IOCTL_STATUS_COMPAT32: + return snd_pcm_status_user32(substream, argp, false); + case SNDRV_PCM_IOCTL_STATUS_EXT_COMPAT32: + return snd_pcm_status_user32(substream, argp, true); + case SNDRV_PCM_IOCTL_CHANNEL_INFO32: + return snd_pcm_ioctl_channel_info_compat(substream, argp); + case SNDRV_PCM_IOCTL_WRITEI_FRAMES32: + return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp); + case SNDRV_PCM_IOCTL_READI_FRAMES32: + return snd_pcm_ioctl_xferi_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp); + case SNDRV_PCM_IOCTL_WRITEN_FRAMES32: + return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_PLAYBACK, argp); + case SNDRV_PCM_IOCTL_READN_FRAMES32: + return snd_pcm_ioctl_xfern_compat(substream, SNDRV_PCM_STREAM_CAPTURE, argp); + case SNDRV_PCM_IOCTL_DELAY32: + return snd_pcm_ioctl_delay_compat(substream, argp); + case SNDRV_PCM_IOCTL_REWIND32: + return snd_pcm_ioctl_rewind_compat(substream, argp); + case SNDRV_PCM_IOCTL_FORWARD32: + return snd_pcm_ioctl_forward_compat(substream, argp); + case SNDRV_PCM_IOCTL_STATUS_COMPAT64: + return snd_pcm_status_user_compat64(substream, argp, false); + case SNDRV_PCM_IOCTL_STATUS_EXT_COMPAT64: + return snd_pcm_status_user_compat64(substream, argp, true); +#ifdef CONFIG_X86_X32_ABI + case SNDRV_PCM_IOCTL_CHANNEL_INFO_X32: + return snd_pcm_ioctl_channel_info_x32(substream, argp); +#endif /* CONFIG_X86_X32_ABI */ + } + + return -ENOIOCTLCMD; +} diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c new file mode 100644 index 0000000000..494ec0c207 --- /dev/null +++ b/sound/core/pcm_dmaengine.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2012, Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Based on: + * imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de> + * mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc. + * ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org> + * Copyright (C) 2006 Applied Data Systems + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/dmaengine.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <sound/dmaengine_pcm.h> + +struct dmaengine_pcm_runtime_data { + struct dma_chan *dma_chan; + dma_cookie_t cookie; + + unsigned int pos; +}; + +static inline struct dmaengine_pcm_runtime_data *substream_to_prtd( + const struct snd_pcm_substream *substream) +{ + return substream->runtime->private_data; +} + +struct dma_chan *snd_dmaengine_pcm_get_chan(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + return prtd->dma_chan; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan); + +/** + * snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config + * @substream: PCM substream + * @params: hw_params + * @slave_config: DMA slave config + * + * This function can be used to initialize a dma_slave_config from a substream + * and hw_params in a dmaengine based PCM driver implementation. + * + * Return: zero if successful, or a negative error code + */ +int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream, + const struct snd_pcm_hw_params *params, + struct dma_slave_config *slave_config) +{ + enum dma_slave_buswidth buswidth; + int bits; + + bits = params_physical_width(params); + if (bits < 8 || bits > 64) + return -EINVAL; + else if (bits == 8) + buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + else if (bits == 16) + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + else if (bits == 24) + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + else if (bits <= 32) + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + else + buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + slave_config->direction = DMA_MEM_TO_DEV; + slave_config->dst_addr_width = buswidth; + } else { + slave_config->direction = DMA_DEV_TO_MEM; + slave_config->src_addr_width = buswidth; + } + + slave_config->device_fc = false; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hwparams_to_dma_slave_config); + +/** + * snd_dmaengine_pcm_set_config_from_dai_data() - Initializes a dma slave config + * using DAI DMA data. + * @substream: PCM substream + * @dma_data: DAI DMA data + * @slave_config: DMA slave configuration + * + * Initializes the {dst,src}_addr, {dst,src}_maxburst, {dst,src}_addr_width + * fields of the DMA slave config from the same fields of the DAI DMA + * data struct. The src and dst fields will be initialized depending on the + * direction of the substream. If the substream is a playback stream the dst + * fields will be initialized, if it is a capture stream the src fields will be + * initialized. The {dst,src}_addr_width field will only be initialized if the + * SND_DMAENGINE_PCM_DAI_FLAG_PACK flag is set or if the addr_width field of + * the DAI DMA data struct is not equal to DMA_SLAVE_BUSWIDTH_UNDEFINED. If + * both conditions are met the latter takes priority. + */ +void snd_dmaengine_pcm_set_config_from_dai_data( + const struct snd_pcm_substream *substream, + const struct snd_dmaengine_dai_dma_data *dma_data, + struct dma_slave_config *slave_config) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + slave_config->dst_addr = dma_data->addr; + slave_config->dst_maxburst = dma_data->maxburst; + if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK) + slave_config->dst_addr_width = + DMA_SLAVE_BUSWIDTH_UNDEFINED; + if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED) + slave_config->dst_addr_width = dma_data->addr_width; + } else { + slave_config->src_addr = dma_data->addr; + slave_config->src_maxburst = dma_data->maxburst; + if (dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK) + slave_config->src_addr_width = + DMA_SLAVE_BUSWIDTH_UNDEFINED; + if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED) + slave_config->src_addr_width = dma_data->addr_width; + } + + slave_config->peripheral_config = dma_data->peripheral_config; + slave_config->peripheral_size = dma_data->peripheral_size; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data); + +static void dmaengine_pcm_dma_complete(void *arg) +{ + unsigned int new_pos; + struct snd_pcm_substream *substream = arg; + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + new_pos = prtd->pos + snd_pcm_lib_period_bytes(substream); + if (new_pos >= snd_pcm_lib_buffer_bytes(substream)) + new_pos = 0; + prtd->pos = new_pos; + + snd_pcm_period_elapsed(substream); +} + +static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + struct dma_chan *chan = prtd->dma_chan; + struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction direction; + unsigned long flags = DMA_CTRL_ACK; + + direction = snd_pcm_substream_to_dma_direction(substream); + + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + prtd->pos = 0; + desc = dmaengine_prep_dma_cyclic(chan, + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), direction, flags); + + if (!desc) + return -ENOMEM; + + desc->callback = dmaengine_pcm_dma_complete; + desc->callback_param = substream; + prtd->cookie = dmaengine_submit(desc); + + return 0; +} + +/** + * snd_dmaengine_pcm_trigger - dmaengine based PCM trigger implementation + * @substream: PCM substream + * @cmd: Trigger command + * + * This function can be used as the PCM trigger callback for dmaengine based PCM + * driver implementations. + * + * Return: 0 on success, a negative error code otherwise + */ +int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = dmaengine_pcm_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dmaengine_resume(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (runtime->info & SNDRV_PCM_INFO_PAUSE) + dmaengine_pause(prtd->dma_chan); + else + dmaengine_terminate_async(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_pause(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_STOP: + dmaengine_terminate_async(prtd->dma_chan); + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger); + +/** + * snd_dmaengine_pcm_pointer_no_residue - dmaengine based PCM pointer implementation + * @substream: PCM substream + * + * This function is deprecated and should not be used by new drivers, as its + * results may be unreliable. + * + * Return: PCM position in frames + */ +snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + return bytes_to_frames(substream->runtime, prtd->pos); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue); + +/** + * snd_dmaengine_pcm_pointer - dmaengine based PCM pointer implementation + * @substream: PCM substream + * + * This function can be used as the PCM pointer callback for dmaengine based PCM + * driver implementations. + * + * Return: PCM position in frames + */ +snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_tx_state state; + enum dma_status status; + unsigned int buf_size; + unsigned int pos = 0; + + status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state); + if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) { + buf_size = snd_pcm_lib_buffer_bytes(substream); + if (state.residue > 0 && state.residue <= buf_size) + pos = buf_size - state.residue; + + runtime->delay = bytes_to_frames(runtime, + state.in_flight_bytes); + } + + return bytes_to_frames(runtime, pos); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer); + +/** + * snd_dmaengine_pcm_request_channel - Request channel for the dmaengine PCM + * @filter_fn: Filter function used to request the DMA channel + * @filter_data: Data passed to the DMA filter function + * + * This function request a DMA channel for usage with dmaengine PCM. + * + * Return: NULL or the requested DMA channel + */ +struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn, + void *filter_data) +{ + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dma_cap_set(DMA_CYCLIC, mask); + + return dma_request_channel(mask, filter_fn, filter_data); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel); + +/** + * snd_dmaengine_pcm_open - Open a dmaengine based PCM substream + * @substream: PCM substream + * @chan: DMA channel to use for data transfers + * + * The function should usually be called from the pcm open callback. Note that + * this function will use private_data field of the substream's runtime. So it + * is not available to your pcm driver implementation. + * + * Return: 0 on success, a negative error code otherwise + */ +int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream, + struct dma_chan *chan) +{ + struct dmaengine_pcm_runtime_data *prtd; + int ret; + + if (!chan) + return -ENXIO; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + return -ENOMEM; + + prtd->dma_chan = chan; + + substream->runtime->private_data = prtd; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open); + +/** + * snd_dmaengine_pcm_open_request_chan - Open a dmaengine based PCM substream and request channel + * @substream: PCM substream + * @filter_fn: Filter function used to request the DMA channel + * @filter_data: Data passed to the DMA filter function + * + * This function will request a DMA channel using the passed filter function and + * data. The function should usually be called from the pcm open callback. Note + * that this function will use private_data field of the substream's runtime. So + * it is not available to your pcm driver implementation. + * + * Return: 0 on success, a negative error code otherwise + */ +int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream, + dma_filter_fn filter_fn, void *filter_data) +{ + return snd_dmaengine_pcm_open(substream, + snd_dmaengine_pcm_request_channel(filter_fn, filter_data)); +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan); + +/** + * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream + * @substream: PCM substream + * + * Return: 0 on success, a negative error code otherwise + */ +int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + dmaengine_synchronize(prtd->dma_chan); + kfree(prtd); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close); + +/** + * snd_dmaengine_pcm_close_release_chan - Close a dmaengine based PCM + * substream and release channel + * @substream: PCM substream + * + * Releases the DMA channel associated with the PCM substream. + * + * Return: zero if successful, or a negative error code + */ +int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream) +{ + struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); + + dmaengine_synchronize(prtd->dma_chan); + dma_release_channel(prtd->dma_chan); + kfree(prtd); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan); + +/** + * snd_dmaengine_pcm_refine_runtime_hwparams - Refine runtime hw params + * @substream: PCM substream + * @dma_data: DAI DMA data + * @hw: PCM hw params + * @chan: DMA channel to use for data transfers + * + * This function will query DMA capability, then refine the pcm hardware + * parameters. + * + * Return: 0 on success, a negative error code otherwise + */ +int snd_dmaengine_pcm_refine_runtime_hwparams( + struct snd_pcm_substream *substream, + struct snd_dmaengine_dai_dma_data *dma_data, + struct snd_pcm_hardware *hw, + struct dma_chan *chan) +{ + struct dma_slave_caps dma_caps; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + snd_pcm_format_t i; + int ret = 0; + + if (!hw || !chan || !dma_data) + return -EINVAL; + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause && dma_caps.cmd_resume) + hw->info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + hw->info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + pcm_for_each_format(i) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + hw->formats |= pcm_format_to_bits(i); + break; + default: + /* Unsupported types */ + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_refine_runtime_hwparams); + +MODULE_LICENSE("GPL"); diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c new file mode 100644 index 0000000000..0707507197 --- /dev/null +++ b/sound/core/pcm_drm_eld.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PCM DRM helpers + */ +#include <linux/bitfield.h> +#include <linux/export.h> +#include <linux/hdmi.h> +#include <drm/drm_edid.h> +#include <sound/pcm.h> +#include <sound/pcm_drm_eld.h> + +#define SAD0_CHANNELS_MASK GENMASK(2, 0) /* max number of channels - 1 */ +#define SAD0_FORMAT_MASK GENMASK(6, 3) /* audio format */ + +#define SAD1_RATE_MASK GENMASK(6, 0) /* bitfield of supported rates */ +#define SAD1_RATE_32000_MASK BIT(0) +#define SAD1_RATE_44100_MASK BIT(1) +#define SAD1_RATE_48000_MASK BIT(2) +#define SAD1_RATE_88200_MASK BIT(3) +#define SAD1_RATE_96000_MASK BIT(4) +#define SAD1_RATE_176400_MASK BIT(5) +#define SAD1_RATE_192000_MASK BIT(6) + +static const unsigned int eld_rates[] = { + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +static unsigned int map_rate_families(const u8 *sad, + unsigned int mask_32000, + unsigned int mask_44100, + unsigned int mask_48000) +{ + unsigned int rate_mask = 0; + + if (sad[1] & SAD1_RATE_32000_MASK) + rate_mask |= mask_32000; + if (sad[1] & (SAD1_RATE_44100_MASK | SAD1_RATE_88200_MASK | SAD1_RATE_176400_MASK)) + rate_mask |= mask_44100; + if (sad[1] & (SAD1_RATE_48000_MASK | SAD1_RATE_96000_MASK | SAD1_RATE_192000_MASK)) + rate_mask |= mask_48000; + return rate_mask; +} + +static unsigned int sad_rate_mask(const u8 *sad) +{ + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return sad[1] & SAD1_RATE_MASK; + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + return map_rate_families(sad, + SAD1_RATE_32000_MASK, + SAD1_RATE_44100_MASK, + SAD1_RATE_48000_MASK); + case HDMI_AUDIO_CODING_TYPE_EAC3: + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return map_rate_families(sad, + 0, + SAD1_RATE_176400_MASK, + SAD1_RATE_192000_MASK); + default: + /* TODO adjust for other compressed formats as well */ + return sad[1] & SAD1_RATE_MASK; + } +} + +static unsigned int sad_max_channels(const u8 *sad) +{ + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + case HDMI_AUDIO_CODING_TYPE_EAC3: + return 2; + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return 8; + default: + /* TODO adjust for other compressed formats as well */ + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + } +} + +static int eld_limit_rates(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = hw_param_interval(params, rule->var); + const struct snd_interval *c; + unsigned int rate_mask = 7, i; + const u8 *sad, *eld = rule->private; + + sad = drm_eld_sad(eld); + if (sad) { + c = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + + for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) { + unsigned max_channels = sad_max_channels(sad); + + /* + * Exclude SADs which do not include the + * requested number of channels. + */ + if (c->min <= max_channels) + rate_mask |= sad_rate_mask(sad); + } + } + + return snd_interval_list(r, ARRAY_SIZE(eld_rates), eld_rates, + rate_mask); +} + +static int eld_limit_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *c = hw_param_interval(params, rule->var); + const struct snd_interval *r; + struct snd_interval t = { .min = 1, .max = 2, .integer = 1, }; + unsigned int i; + const u8 *sad, *eld = rule->private; + + sad = drm_eld_sad(eld); + if (sad) { + unsigned int rate_mask = 0; + + /* Convert the rate interval to a mask */ + r = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) + if (r->min <= eld_rates[i] && r->max >= eld_rates[i]) + rate_mask |= BIT(i); + + for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) + if (rate_mask & sad_rate_mask(sad)) + t.max = max(t.max, sad_max_channels(sad)); + } + + return snd_interval_refine(c, &t); +} + +int snd_pcm_hw_constraint_eld(struct snd_pcm_runtime *runtime, void *eld) +{ + int ret; + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + eld_limit_rates, eld, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) + return ret; + + ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + eld_limit_channels, eld, + SNDRV_PCM_HW_PARAM_RATE, -1); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_pcm_hw_constraint_eld); diff --git a/sound/core/pcm_iec958.c b/sound/core/pcm_iec958.c new file mode 100644 index 0000000000..7a1b816f67 --- /dev/null +++ b/sound/core/pcm_iec958.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PCM DRM helpers + */ +#include <linux/export.h> +#include <linux/types.h> +#include <sound/asoundef.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/pcm_iec958.h> + +/** + * snd_pcm_create_iec958_consumer_default - create default consumer format IEC958 channel status + * @cs: channel status buffer, at least four bytes + * @len: length of channel status buffer + * + * Create the consumer format channel status data in @cs of maximum size + * @len. When relevant, the configuration-dependant bits will be set as + * unspecified. + * + * Drivers should then call einter snd_pcm_fill_iec958_consumer() or + * snd_pcm_fill_iec958_consumer_hw_params() to replace these unspecified + * bits by their actual values. + * + * Drivers may wish to tweak the contents of the buffer after creation. + * + * Returns: length of buffer, or negative error code if something failed. + */ +int snd_pcm_create_iec958_consumer_default(u8 *cs, size_t len) +{ + if (len < 4) + return -EINVAL; + + memset(cs, 0, len); + + cs[0] = IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS_NONE; + cs[1] = IEC958_AES1_CON_GENERAL; + cs[2] = IEC958_AES2_CON_SOURCE_UNSPEC | IEC958_AES2_CON_CHANNEL_UNSPEC; + cs[3] = IEC958_AES3_CON_CLOCK_1000PPM | IEC958_AES3_CON_FS_NOTID; + + if (len > 4) + cs[4] = IEC958_AES4_CON_WORDLEN_NOTID; + + return len; +} +EXPORT_SYMBOL_GPL(snd_pcm_create_iec958_consumer_default); + +static int fill_iec958_consumer(uint rate, uint sample_width, + u8 *cs, size_t len) +{ + if (len < 4) + return -EINVAL; + + if ((cs[3] & IEC958_AES3_CON_FS) == IEC958_AES3_CON_FS_NOTID) { + unsigned int fs; + + switch (rate) { + case 32000: + fs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + fs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + fs = IEC958_AES3_CON_FS_48000; + break; + case 88200: + fs = IEC958_AES3_CON_FS_88200; + break; + case 96000: + fs = IEC958_AES3_CON_FS_96000; + break; + case 176400: + fs = IEC958_AES3_CON_FS_176400; + break; + case 192000: + fs = IEC958_AES3_CON_FS_192000; + break; + default: + return -EINVAL; + } + + cs[3] &= ~IEC958_AES3_CON_FS; + cs[3] |= fs; + } + + if (len > 4 && + (cs[4] & IEC958_AES4_CON_WORDLEN) == IEC958_AES4_CON_WORDLEN_NOTID) { + unsigned int ws; + + switch (sample_width) { + case 16: + ws = IEC958_AES4_CON_WORDLEN_20_16; + break; + case 18: + ws = IEC958_AES4_CON_WORDLEN_22_18; + break; + case 20: + ws = IEC958_AES4_CON_WORDLEN_20_16 | + IEC958_AES4_CON_MAX_WORDLEN_24; + break; + case 24: + case 32: /* Assume 24-bit width for 32-bit samples. */ + ws = IEC958_AES4_CON_WORDLEN_24_20 | + IEC958_AES4_CON_MAX_WORDLEN_24; + break; + + default: + return -EINVAL; + } + + cs[4] &= ~IEC958_AES4_CON_WORDLEN; + cs[4] |= ws; + } + + return len; +} + +/** + * snd_pcm_fill_iec958_consumer - Fill consumer format IEC958 channel status + * @runtime: pcm runtime structure with ->rate filled in + * @cs: channel status buffer, at least four bytes + * @len: length of channel status buffer + * + * Fill the unspecified bits in an IEC958 status bits array using the + * parameters of the PCM runtime @runtime. + * + * Drivers may wish to tweak the contents of the buffer after its been + * filled. + * + * Returns: length of buffer, or negative error code if something failed. + */ +int snd_pcm_fill_iec958_consumer(struct snd_pcm_runtime *runtime, + u8 *cs, size_t len) +{ + return fill_iec958_consumer(runtime->rate, + snd_pcm_format_width(runtime->format), + cs, len); +} +EXPORT_SYMBOL_GPL(snd_pcm_fill_iec958_consumer); + +/** + * snd_pcm_fill_iec958_consumer_hw_params - Fill consumer format IEC958 channel status + * @params: the hw_params instance for extracting rate and sample format + * @cs: channel status buffer, at least four bytes + * @len: length of channel status buffer + * + * Fill the unspecified bits in an IEC958 status bits array using the + * parameters of the PCM hardware parameters @params. + * + * Drivers may wish to tweak the contents of the buffer after its been + * filled.. + * + * Returns: length of buffer, or negative error code if something failed. + */ +int snd_pcm_fill_iec958_consumer_hw_params(struct snd_pcm_hw_params *params, + u8 *cs, size_t len) +{ + return fill_iec958_consumer(params_rate(params), params_width(params), cs, len); +} +EXPORT_SYMBOL_GPL(snd_pcm_fill_iec958_consumer_hw_params); + +/** + * snd_pcm_create_iec958_consumer - create consumer format IEC958 channel status + * @runtime: pcm runtime structure with ->rate filled in + * @cs: channel status buffer, at least four bytes + * @len: length of channel status buffer + * + * Create the consumer format channel status data in @cs of maximum size + * @len corresponding to the parameters of the PCM runtime @runtime. + * + * Drivers may wish to tweak the contents of the buffer after creation. + * + * Returns: length of buffer, or negative error code if something failed. + */ +int snd_pcm_create_iec958_consumer(struct snd_pcm_runtime *runtime, u8 *cs, + size_t len) +{ + int ret; + + ret = snd_pcm_create_iec958_consumer_default(cs, len); + if (ret < 0) + return ret; + + return snd_pcm_fill_iec958_consumer(runtime, cs, len); +} +EXPORT_SYMBOL(snd_pcm_create_iec958_consumer); + +/** + * snd_pcm_create_iec958_consumer_hw_params - create IEC958 channel status + * @params: the hw_params instance for extracting rate and sample format + * @cs: channel status buffer, at least four bytes + * @len: length of channel status buffer + * + * Create the consumer format channel status data in @cs of maximum size + * @len corresponding to the parameters of the PCM runtime @runtime. + * + * Drivers may wish to tweak the contents of the buffer after creation. + * + * Returns: length of buffer, or negative error code if something failed. + */ +int snd_pcm_create_iec958_consumer_hw_params(struct snd_pcm_hw_params *params, + u8 *cs, size_t len) +{ + int ret; + + ret = snd_pcm_create_iec958_consumer_default(cs, len); + if (ret < 0) + return ret; + + return fill_iec958_consumer(params_rate(params), params_width(params), cs, len); +} +EXPORT_SYMBOL(snd_pcm_create_iec958_consumer_hw_params); diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c new file mode 100644 index 0000000000..a11cd7d629 --- /dev/null +++ b/sound/core/pcm_lib.c @@ -0,0 +1,2567 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Abramo Bagnara <abramo@alsa-project.org> + */ + +#include <linux/slab.h> +#include <linux/sched/signal.h> +#include <linux/time.h> +#include <linux/math64.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/timer.h> + +#include "pcm_local.h" + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG +#define CREATE_TRACE_POINTS +#include "pcm_trace.h" +#else +#define trace_hwptr(substream, pos, in_interrupt) +#define trace_xrun(substream) +#define trace_hw_ptr_error(substream, reason) +#define trace_applptr(substream, prev, curr) +#endif + +static int fill_silence_frames(struct snd_pcm_substream *substream, + snd_pcm_uframes_t off, snd_pcm_uframes_t frames); + + +static inline void update_silence_vars(struct snd_pcm_runtime *runtime, + snd_pcm_uframes_t ptr, + snd_pcm_uframes_t new_ptr) +{ + snd_pcm_sframes_t delta; + + delta = new_ptr - ptr; + if (delta == 0) + return; + if (delta < 0) + delta += runtime->boundary; + if ((snd_pcm_uframes_t)delta < runtime->silence_filled) + runtime->silence_filled -= delta; + else + runtime->silence_filled = 0; + runtime->silence_start = new_ptr; +} + +/* + * fill ring buffer with silence + * runtime->silence_start: starting pointer to silence area + * runtime->silence_filled: size filled with silence + * runtime->silence_threshold: threshold from application + * runtime->silence_size: maximal size from application + * + * when runtime->silence_size >= runtime->boundary - fill processed area with silence immediately + */ +void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_uframes_t new_hw_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t frames, ofs, transfer; + int err; + + if (runtime->silence_size < runtime->boundary) { + snd_pcm_sframes_t noise_dist; + snd_pcm_uframes_t appl_ptr = READ_ONCE(runtime->control->appl_ptr); + update_silence_vars(runtime, runtime->silence_start, appl_ptr); + /* initialization outside pointer updates */ + if (new_hw_ptr == ULONG_MAX) + new_hw_ptr = runtime->status->hw_ptr; + /* get hw_avail with the boundary crossing */ + noise_dist = appl_ptr - new_hw_ptr; + if (noise_dist < 0) + noise_dist += runtime->boundary; + /* total noise distance */ + noise_dist += runtime->silence_filled; + if (noise_dist >= (snd_pcm_sframes_t) runtime->silence_threshold) + return; + frames = runtime->silence_threshold - noise_dist; + if (frames > runtime->silence_size) + frames = runtime->silence_size; + } else { + /* + * This filling mode aims at free-running mode (used for example by dmix), + * which doesn't update the application pointer. + */ + snd_pcm_uframes_t hw_ptr = runtime->status->hw_ptr; + if (new_hw_ptr == ULONG_MAX) { + /* + * Initialization, fill the whole unused buffer with silence. + * + * Usually, this is entered while stopped, before data is queued, + * so both pointers are expected to be zero. + */ + snd_pcm_sframes_t avail = runtime->control->appl_ptr - hw_ptr; + if (avail < 0) + avail += runtime->boundary; + /* + * In free-running mode, appl_ptr will be zero even while running, + * so we end up with a huge number. There is no useful way to + * handle this, so we just clear the whole buffer. + */ + runtime->silence_filled = avail > runtime->buffer_size ? 0 : avail; + runtime->silence_start = hw_ptr; + } else { + /* Silence the just played area immediately */ + update_silence_vars(runtime, hw_ptr, new_hw_ptr); + } + /* + * In this mode, silence_filled actually includes the valid + * sample data from the user. + */ + frames = runtime->buffer_size - runtime->silence_filled; + } + if (snd_BUG_ON(frames > runtime->buffer_size)) + return; + if (frames == 0) + return; + ofs = (runtime->silence_start + runtime->silence_filled) % runtime->buffer_size; + do { + transfer = ofs + frames > runtime->buffer_size ? runtime->buffer_size - ofs : frames; + err = fill_silence_frames(substream, ofs, transfer); + snd_BUG_ON(err < 0); + runtime->silence_filled += transfer; + frames -= transfer; + ofs = 0; + } while (frames > 0); + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); +} + +#ifdef CONFIG_SND_DEBUG +void snd_pcm_debug_name(struct snd_pcm_substream *substream, + char *name, size_t len) +{ + snprintf(name, len, "pcmC%dD%d%c:%d", + substream->pcm->card->number, + substream->pcm->device, + substream->stream ? 'c' : 'p', + substream->number); +} +EXPORT_SYMBOL(snd_pcm_debug_name); +#endif + +#define XRUN_DEBUG_BASIC (1<<0) +#define XRUN_DEBUG_STACK (1<<1) /* dump also stack */ +#define XRUN_DEBUG_JIFFIESCHECK (1<<2) /* do jiffies check */ + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG + +#define xrun_debug(substream, mask) \ + ((substream)->pstr->xrun_debug & (mask)) +#else +#define xrun_debug(substream, mask) 0 +#endif + +#define dump_stack_on_xrun(substream) do { \ + if (xrun_debug(substream, XRUN_DEBUG_STACK)) \ + dump_stack(); \ + } while (0) + +/* call with stream lock held */ +void __snd_pcm_xrun(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + trace_xrun(substream); + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { + struct timespec64 tstamp; + + snd_pcm_gettime(runtime, &tstamp); + runtime->status->tstamp.tv_sec = tstamp.tv_sec; + runtime->status->tstamp.tv_nsec = tstamp.tv_nsec; + } + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { + char name[16]; + snd_pcm_debug_name(substream, name, sizeof(name)); + pcm_warn(substream->pcm, "XRUN: %s\n", name); + dump_stack_on_xrun(substream); + } +} + +#ifdef CONFIG_SND_PCM_XRUN_DEBUG +#define hw_ptr_error(substream, in_interrupt, reason, fmt, args...) \ + do { \ + trace_hw_ptr_error(substream, reason); \ + if (xrun_debug(substream, XRUN_DEBUG_BASIC)) { \ + pr_err_ratelimited("ALSA: PCM: [%c] " reason ": " fmt, \ + (in_interrupt) ? 'Q' : 'P', ##args); \ + dump_stack_on_xrun(substream); \ + } \ + } while (0) + +#else /* ! CONFIG_SND_PCM_XRUN_DEBUG */ + +#define hw_ptr_error(substream, fmt, args...) do { } while (0) + +#endif + +int snd_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime) +{ + snd_pcm_uframes_t avail; + + avail = snd_pcm_avail(substream); + if (avail > runtime->avail_max) + runtime->avail_max = avail; + if (runtime->state == SNDRV_PCM_STATE_DRAINING) { + if (avail >= runtime->buffer_size) { + snd_pcm_drain_done(substream); + return -EPIPE; + } + } else { + if (avail >= runtime->stop_threshold) { + __snd_pcm_xrun(substream); + return -EPIPE; + } + } + if (runtime->twake) { + if (avail >= runtime->twake) + wake_up(&runtime->tsleep); + } else if (avail >= runtime->control->avail_min) + wake_up(&runtime->sleep); + return 0; +} + +static void update_audio_tstamp(struct snd_pcm_substream *substream, + struct timespec64 *curr_tstamp, + struct timespec64 *audio_tstamp) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + u64 audio_frames, audio_nsecs; + struct timespec64 driver_tstamp; + + if (runtime->tstamp_mode != SNDRV_PCM_TSTAMP_ENABLE) + return; + + if (!(substream->ops->get_time_info) || + (runtime->audio_tstamp_report.actual_type == + SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) { + + /* + * provide audio timestamp derived from pointer position + * add delay only if requested + */ + + audio_frames = runtime->hw_ptr_wrap + runtime->status->hw_ptr; + + if (runtime->audio_tstamp_config.report_delay) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + audio_frames -= runtime->delay; + else + audio_frames += runtime->delay; + } + audio_nsecs = div_u64(audio_frames * 1000000000LL, + runtime->rate); + *audio_tstamp = ns_to_timespec64(audio_nsecs); + } + + if (runtime->status->audio_tstamp.tv_sec != audio_tstamp->tv_sec || + runtime->status->audio_tstamp.tv_nsec != audio_tstamp->tv_nsec) { + runtime->status->audio_tstamp.tv_sec = audio_tstamp->tv_sec; + runtime->status->audio_tstamp.tv_nsec = audio_tstamp->tv_nsec; + runtime->status->tstamp.tv_sec = curr_tstamp->tv_sec; + runtime->status->tstamp.tv_nsec = curr_tstamp->tv_nsec; + } + + + /* + * re-take a driver timestamp to let apps detect if the reference tstamp + * read by low-level hardware was provided with a delay + */ + snd_pcm_gettime(substream->runtime, &driver_tstamp); + runtime->driver_tstamp = driver_tstamp; +} + +static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream, + unsigned int in_interrupt) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t pos; + snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base; + snd_pcm_sframes_t hdelta, delta; + unsigned long jdelta; + unsigned long curr_jiffies; + struct timespec64 curr_tstamp; + struct timespec64 audio_tstamp; + int crossed_boundary = 0; + + old_hw_ptr = runtime->status->hw_ptr; + + /* + * group pointer, time and jiffies reads to allow for more + * accurate correlations/corrections. + * The values are stored at the end of this routine after + * corrections for hw_ptr position + */ + pos = substream->ops->pointer(substream); + curr_jiffies = jiffies; + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { + if ((substream->ops->get_time_info) && + (runtime->audio_tstamp_config.type_requested != SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT)) { + substream->ops->get_time_info(substream, &curr_tstamp, + &audio_tstamp, + &runtime->audio_tstamp_config, + &runtime->audio_tstamp_report); + + /* re-test in case tstamp type is not supported in hardware and was demoted to DEFAULT */ + if (runtime->audio_tstamp_report.actual_type == SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT) + snd_pcm_gettime(runtime, &curr_tstamp); + } else + snd_pcm_gettime(runtime, &curr_tstamp); + } + + if (pos == SNDRV_PCM_POS_XRUN) { + __snd_pcm_xrun(substream); + return -EPIPE; + } + if (pos >= runtime->buffer_size) { + if (printk_ratelimit()) { + char name[16]; + snd_pcm_debug_name(substream, name, sizeof(name)); + pcm_err(substream->pcm, + "invalid position: %s, pos = %ld, buffer size = %ld, period size = %ld\n", + name, pos, runtime->buffer_size, + runtime->period_size); + } + pos = 0; + } + pos -= pos % runtime->min_align; + trace_hwptr(substream, pos, in_interrupt); + hw_base = runtime->hw_ptr_base; + new_hw_ptr = hw_base + pos; + if (in_interrupt) { + /* we know that one period was processed */ + /* delta = "expected next hw_ptr" for in_interrupt != 0 */ + delta = runtime->hw_ptr_interrupt + runtime->period_size; + if (delta > new_hw_ptr) { + /* check for double acknowledged interrupts */ + hdelta = curr_jiffies - runtime->hw_ptr_jiffies; + if (hdelta > runtime->hw_ptr_buffer_jiffies/2 + 1) { + hw_base += runtime->buffer_size; + if (hw_base >= runtime->boundary) { + hw_base = 0; + crossed_boundary++; + } + new_hw_ptr = hw_base + pos; + goto __delta; + } + } + } + /* new_hw_ptr might be lower than old_hw_ptr in case when */ + /* pointer crosses the end of the ring buffer */ + if (new_hw_ptr < old_hw_ptr) { + hw_base += runtime->buffer_size; + if (hw_base >= runtime->boundary) { + hw_base = 0; + crossed_boundary++; + } + new_hw_ptr = hw_base + pos; + } + __delta: + delta = new_hw_ptr - old_hw_ptr; + if (delta < 0) + delta += runtime->boundary; + + if (runtime->no_period_wakeup) { + snd_pcm_sframes_t xrun_threshold; + /* + * Without regular period interrupts, we have to check + * the elapsed time to detect xruns. + */ + jdelta = curr_jiffies - runtime->hw_ptr_jiffies; + if (jdelta < runtime->hw_ptr_buffer_jiffies / 2) + goto no_delta_check; + hdelta = jdelta - delta * HZ / runtime->rate; + xrun_threshold = runtime->hw_ptr_buffer_jiffies / 2 + 1; + while (hdelta > xrun_threshold) { + delta += runtime->buffer_size; + hw_base += runtime->buffer_size; + if (hw_base >= runtime->boundary) { + hw_base = 0; + crossed_boundary++; + } + new_hw_ptr = hw_base + pos; + hdelta -= runtime->hw_ptr_buffer_jiffies; + } + goto no_delta_check; + } + + /* something must be really wrong */ + if (delta >= runtime->buffer_size + runtime->period_size) { + hw_ptr_error(substream, in_interrupt, "Unexpected hw_ptr", + "(stream=%i, pos=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n", + substream->stream, (long)pos, + (long)new_hw_ptr, (long)old_hw_ptr); + return 0; + } + + /* Do jiffies check only in xrun_debug mode */ + if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK)) + goto no_jiffies_check; + + /* Skip the jiffies check for hardwares with BATCH flag. + * Such hardware usually just increases the position at each IRQ, + * thus it can't give any strange position. + */ + if (runtime->hw.info & SNDRV_PCM_INFO_BATCH) + goto no_jiffies_check; + hdelta = delta; + if (hdelta < runtime->delay) + goto no_jiffies_check; + hdelta -= runtime->delay; + jdelta = curr_jiffies - runtime->hw_ptr_jiffies; + if (((hdelta * HZ) / runtime->rate) > jdelta + HZ/100) { + delta = jdelta / + (((runtime->period_size * HZ) / runtime->rate) + + HZ/100); + /* move new_hw_ptr according jiffies not pos variable */ + new_hw_ptr = old_hw_ptr; + hw_base = delta; + /* use loop to avoid checks for delta overflows */ + /* the delta value is small or zero in most cases */ + while (delta > 0) { + new_hw_ptr += runtime->period_size; + if (new_hw_ptr >= runtime->boundary) { + new_hw_ptr -= runtime->boundary; + crossed_boundary--; + } + delta--; + } + /* align hw_base to buffer_size */ + hw_ptr_error(substream, in_interrupt, "hw_ptr skipping", + "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n", + (long)pos, (long)hdelta, + (long)runtime->period_size, jdelta, + ((hdelta * HZ) / runtime->rate), hw_base, + (unsigned long)old_hw_ptr, + (unsigned long)new_hw_ptr); + /* reset values to proper state */ + delta = 0; + hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size); + } + no_jiffies_check: + if (delta > runtime->period_size + runtime->period_size / 2) { + hw_ptr_error(substream, in_interrupt, + "Lost interrupts?", + "(stream=%i, delta=%ld, new_hw_ptr=%ld, old_hw_ptr=%ld)\n", + substream->stream, (long)delta, + (long)new_hw_ptr, + (long)old_hw_ptr); + } + + no_delta_check: + if (runtime->status->hw_ptr == new_hw_ptr) { + runtime->hw_ptr_jiffies = curr_jiffies; + update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp); + return 0; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, new_hw_ptr); + + if (in_interrupt) { + delta = new_hw_ptr - runtime->hw_ptr_interrupt; + if (delta < 0) + delta += runtime->boundary; + delta -= (snd_pcm_uframes_t)delta % runtime->period_size; + runtime->hw_ptr_interrupt += delta; + if (runtime->hw_ptr_interrupt >= runtime->boundary) + runtime->hw_ptr_interrupt -= runtime->boundary; + } + runtime->hw_ptr_base = hw_base; + runtime->status->hw_ptr = new_hw_ptr; + runtime->hw_ptr_jiffies = curr_jiffies; + if (crossed_boundary) { + snd_BUG_ON(crossed_boundary != 1); + runtime->hw_ptr_wrap += runtime->boundary; + } + + update_audio_tstamp(substream, &curr_tstamp, &audio_tstamp); + + return snd_pcm_update_state(substream, runtime); +} + +/* CAUTION: call it with irq disabled */ +int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream) +{ + return snd_pcm_update_hw_ptr0(substream, 0); +} + +/** + * snd_pcm_set_ops - set the PCM operators + * @pcm: the pcm instance + * @direction: stream direction, SNDRV_PCM_STREAM_XXX + * @ops: the operator table + * + * Sets the given PCM operators to the pcm instance. + */ +void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, + const struct snd_pcm_ops *ops) +{ + struct snd_pcm_str *stream = &pcm->streams[direction]; + struct snd_pcm_substream *substream; + + for (substream = stream->substream; substream != NULL; substream = substream->next) + substream->ops = ops; +} +EXPORT_SYMBOL(snd_pcm_set_ops); + +/** + * snd_pcm_set_sync - set the PCM sync id + * @substream: the pcm substream + * + * Sets the PCM sync identifier for the card. + */ +void snd_pcm_set_sync(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + runtime->sync.id32[0] = substream->pcm->card->number; + runtime->sync.id32[1] = -1; + runtime->sync.id32[2] = -1; + runtime->sync.id32[3] = -1; +} +EXPORT_SYMBOL(snd_pcm_set_sync); + +/* + * Standard ioctl routine + */ + +static inline unsigned int div32(unsigned int a, unsigned int b, + unsigned int *r) +{ + if (b == 0) { + *r = 0; + return UINT_MAX; + } + *r = a % b; + return a / b; +} + +static inline unsigned int div_down(unsigned int a, unsigned int b) +{ + if (b == 0) + return UINT_MAX; + return a / b; +} + +static inline unsigned int div_up(unsigned int a, unsigned int b) +{ + unsigned int r; + unsigned int q; + if (b == 0) + return UINT_MAX; + q = div32(a, b, &r); + if (r) + ++q; + return q; +} + +static inline unsigned int mul(unsigned int a, unsigned int b) +{ + if (a == 0) + return 0; + if (div_down(UINT_MAX, a) < b) + return UINT_MAX; + return a * b; +} + +static inline unsigned int muldiv32(unsigned int a, unsigned int b, + unsigned int c, unsigned int *r) +{ + u_int64_t n = (u_int64_t) a * b; + if (c == 0) { + *r = 0; + return UINT_MAX; + } + n = div_u64_rem(n, c, r); + if (n >= UINT_MAX) { + *r = 0; + return UINT_MAX; + } + return n; +} + +/** + * snd_interval_refine - refine the interval value of configurator + * @i: the interval value to refine + * @v: the interval value to refer to + * + * Refines the interval value with the reference value. + * The interval is changed to the range satisfying both intervals. + * The interval status (min, max, integer, etc.) are evaluated. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_refine(struct snd_interval *i, const struct snd_interval *v) +{ + int changed = 0; + if (snd_BUG_ON(snd_interval_empty(i))) + return -EINVAL; + if (i->min < v->min) { + i->min = v->min; + i->openmin = v->openmin; + changed = 1; + } else if (i->min == v->min && !i->openmin && v->openmin) { + i->openmin = 1; + changed = 1; + } + if (i->max > v->max) { + i->max = v->max; + i->openmax = v->openmax; + changed = 1; + } else if (i->max == v->max && !i->openmax && v->openmax) { + i->openmax = 1; + changed = 1; + } + if (!i->integer && v->integer) { + i->integer = 1; + changed = 1; + } + if (i->integer) { + if (i->openmin) { + i->min++; + i->openmin = 0; + } + if (i->openmax) { + i->max--; + i->openmax = 0; + } + } else if (!i->openmin && !i->openmax && i->min == i->max) + i->integer = 1; + if (snd_interval_checkempty(i)) { + snd_interval_none(i); + return -EINVAL; + } + return changed; +} +EXPORT_SYMBOL(snd_interval_refine); + +static int snd_interval_refine_first(struct snd_interval *i) +{ + const unsigned int last_max = i->max; + + if (snd_BUG_ON(snd_interval_empty(i))) + return -EINVAL; + if (snd_interval_single(i)) + return 0; + i->max = i->min; + if (i->openmin) + i->max++; + /* only exclude max value if also excluded before refine */ + i->openmax = (i->openmax && i->max >= last_max); + return 1; +} + +static int snd_interval_refine_last(struct snd_interval *i) +{ + const unsigned int last_min = i->min; + + if (snd_BUG_ON(snd_interval_empty(i))) + return -EINVAL; + if (snd_interval_single(i)) + return 0; + i->min = i->max; + if (i->openmax) + i->min--; + /* only exclude min value if also excluded before refine */ + i->openmin = (i->openmin && i->min <= last_min); + return 1; +} + +void snd_interval_mul(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *c) +{ + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = mul(a->min, b->min); + c->openmin = (a->openmin || b->openmin); + c->max = mul(a->max, b->max); + c->openmax = (a->openmax || b->openmax); + c->integer = (a->integer && b->integer); +} + +/** + * snd_interval_div - refine the interval value with division + * @a: dividend + * @b: divisor + * @c: quotient + * + * c = a / b + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_div(const struct snd_interval *a, const struct snd_interval *b, struct snd_interval *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = div32(a->min, b->max, &r); + c->openmin = (r || a->openmin || b->openmax); + if (b->min > 0) { + c->max = div32(a->max, b->min, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmin); + } else { + c->max = UINT_MAX; + c->openmax = 0; + } + c->integer = 0; +} + +/** + * snd_interval_muldivk - refine the interval value + * @a: dividend 1 + * @b: dividend 2 + * @k: divisor (as integer) + * @c: result + * + * c = a * b / k + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_muldivk(const struct snd_interval *a, const struct snd_interval *b, + unsigned int k, struct snd_interval *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = muldiv32(a->min, b->min, k, &r); + c->openmin = (r || a->openmin || b->openmin); + c->max = muldiv32(a->max, b->max, k, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmax); + c->integer = 0; +} + +/** + * snd_interval_mulkdiv - refine the interval value + * @a: dividend 1 + * @k: dividend 2 (as integer) + * @b: divisor + * @c: result + * + * c = a * k / b + * + * Returns non-zero if the value is changed, zero if not changed. + */ +void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k, + const struct snd_interval *b, struct snd_interval *c) +{ + unsigned int r; + if (a->empty || b->empty) { + snd_interval_none(c); + return; + } + c->empty = 0; + c->min = muldiv32(a->min, k, b->max, &r); + c->openmin = (r || a->openmin || b->openmax); + if (b->min > 0) { + c->max = muldiv32(a->max, k, b->min, &r); + if (r) { + c->max++; + c->openmax = 1; + } else + c->openmax = (a->openmax || b->openmin); + } else { + c->max = UINT_MAX; + c->openmax = 0; + } + c->integer = 0; +} + +/* ---- */ + + +/** + * snd_interval_ratnum - refine the interval value + * @i: interval to refine + * @rats_count: number of ratnum_t + * @rats: ratnum_t array + * @nump: pointer to store the resultant numerator + * @denp: pointer to store the resultant denominator + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_ratnum(struct snd_interval *i, + unsigned int rats_count, const struct snd_ratnum *rats, + unsigned int *nump, unsigned int *denp) +{ + unsigned int best_num, best_den; + int best_diff; + unsigned int k; + struct snd_interval t; + int err; + unsigned int result_num, result_den; + int result_diff; + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num = rats[k].num; + unsigned int den; + unsigned int q = i->min; + int diff; + if (q == 0) + q = 1; + den = div_up(num, q); + if (den < rats[k].den_min) + continue; + if (den > rats[k].den_max) + den = rats[k].den_max; + else { + unsigned int r; + r = (den - rats[k].den_min) % rats[k].den_step; + if (r != 0) + den -= r; + } + diff = num - q * den; + if (diff < 0) + diff = -diff; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.min = div_down(best_num, best_den); + t.openmin = !!(best_num % best_den); + + result_num = best_num; + result_diff = best_diff; + result_den = best_den; + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num = rats[k].num; + unsigned int den; + unsigned int q = i->max; + int diff; + if (q == 0) { + i->empty = 1; + return -EINVAL; + } + den = div_down(num, q); + if (den > rats[k].den_max) + continue; + if (den < rats[k].den_min) + den = rats[k].den_min; + else { + unsigned int r; + r = (den - rats[k].den_min) % rats[k].den_step; + if (r != 0) + den += rats[k].den_step - r; + } + diff = q * den - num; + if (diff < 0) + diff = -diff; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.max = div_up(best_num, best_den); + t.openmax = !!(best_num % best_den); + t.integer = 0; + err = snd_interval_refine(i, &t); + if (err < 0) + return err; + + if (snd_interval_single(i)) { + if (best_diff * result_den < result_diff * best_den) { + result_num = best_num; + result_den = best_den; + } + if (nump) + *nump = result_num; + if (denp) + *denp = result_den; + } + return err; +} +EXPORT_SYMBOL(snd_interval_ratnum); + +/** + * snd_interval_ratden - refine the interval value + * @i: interval to refine + * @rats_count: number of struct ratden + * @rats: struct ratden array + * @nump: pointer to store the resultant numerator + * @denp: pointer to store the resultant denominator + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +static int snd_interval_ratden(struct snd_interval *i, + unsigned int rats_count, + const struct snd_ratden *rats, + unsigned int *nump, unsigned int *denp) +{ + unsigned int best_num, best_diff, best_den; + unsigned int k; + struct snd_interval t; + int err; + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num; + unsigned int den = rats[k].den; + unsigned int q = i->min; + int diff; + num = mul(q, den); + if (num > rats[k].num_max) + continue; + if (num < rats[k].num_min) + num = rats[k].num_max; + else { + unsigned int r; + r = (num - rats[k].num_min) % rats[k].num_step; + if (r != 0) + num += rats[k].num_step - r; + } + diff = num - q * den; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.min = div_down(best_num, best_den); + t.openmin = !!(best_num % best_den); + + best_num = best_den = best_diff = 0; + for (k = 0; k < rats_count; ++k) { + unsigned int num; + unsigned int den = rats[k].den; + unsigned int q = i->max; + int diff; + num = mul(q, den); + if (num < rats[k].num_min) + continue; + if (num > rats[k].num_max) + num = rats[k].num_max; + else { + unsigned int r; + r = (num - rats[k].num_min) % rats[k].num_step; + if (r != 0) + num -= r; + } + diff = q * den - num; + if (best_num == 0 || + diff * best_den < best_diff * den) { + best_diff = diff; + best_den = den; + best_num = num; + } + } + if (best_den == 0) { + i->empty = 1; + return -EINVAL; + } + t.max = div_up(best_num, best_den); + t.openmax = !!(best_num % best_den); + t.integer = 0; + err = snd_interval_refine(i, &t); + if (err < 0) + return err; + + if (snd_interval_single(i)) { + if (nump) + *nump = best_num; + if (denp) + *denp = best_den; + } + return err; +} + +/** + * snd_interval_list - refine the interval value from the list + * @i: the interval value to refine + * @count: the number of elements in the list + * @list: the value list + * @mask: the bit-mask to evaluate + * + * Refines the interval value from the list. + * When mask is non-zero, only the elements corresponding to bit 1 are + * evaluated. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_list(struct snd_interval *i, unsigned int count, + const unsigned int *list, unsigned int mask) +{ + unsigned int k; + struct snd_interval list_range; + + if (!count) { + i->empty = 1; + return -EINVAL; + } + snd_interval_any(&list_range); + list_range.min = UINT_MAX; + list_range.max = 0; + for (k = 0; k < count; k++) { + if (mask && !(mask & (1 << k))) + continue; + if (!snd_interval_test(i, list[k])) + continue; + list_range.min = min(list_range.min, list[k]); + list_range.max = max(list_range.max, list[k]); + } + return snd_interval_refine(i, &list_range); +} +EXPORT_SYMBOL(snd_interval_list); + +/** + * snd_interval_ranges - refine the interval value from the list of ranges + * @i: the interval value to refine + * @count: the number of elements in the list of ranges + * @ranges: the ranges list + * @mask: the bit-mask to evaluate + * + * Refines the interval value from the list of ranges. + * When mask is non-zero, only the elements corresponding to bit 1 are + * evaluated. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_interval_ranges(struct snd_interval *i, unsigned int count, + const struct snd_interval *ranges, unsigned int mask) +{ + unsigned int k; + struct snd_interval range_union; + struct snd_interval range; + + if (!count) { + snd_interval_none(i); + return -EINVAL; + } + snd_interval_any(&range_union); + range_union.min = UINT_MAX; + range_union.max = 0; + for (k = 0; k < count; k++) { + if (mask && !(mask & (1 << k))) + continue; + snd_interval_copy(&range, &ranges[k]); + if (snd_interval_refine(&range, i) < 0) + continue; + if (snd_interval_empty(&range)) + continue; + + if (range.min < range_union.min) { + range_union.min = range.min; + range_union.openmin = 1; + } + if (range.min == range_union.min && !range.openmin) + range_union.openmin = 0; + if (range.max > range_union.max) { + range_union.max = range.max; + range_union.openmax = 1; + } + if (range.max == range_union.max && !range.openmax) + range_union.openmax = 0; + } + return snd_interval_refine(i, &range_union); +} +EXPORT_SYMBOL(snd_interval_ranges); + +static int snd_interval_step(struct snd_interval *i, unsigned int step) +{ + unsigned int n; + int changed = 0; + n = i->min % step; + if (n != 0 || i->openmin) { + i->min += step - n; + i->openmin = 0; + changed = 1; + } + n = i->max % step; + if (n != 0 || i->openmax) { + i->max -= n; + i->openmax = 0; + changed = 1; + } + if (snd_interval_checkempty(i)) { + i->empty = 1; + return -EINVAL; + } + return changed; +} + +/* Info constraints helpers */ + +/** + * snd_pcm_hw_rule_add - add the hw-constraint rule + * @runtime: the pcm runtime instance + * @cond: condition bits + * @var: the variable to evaluate + * @func: the evaluation function + * @private: the private data pointer passed to function + * @dep: the dependent variables + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_rule_add(struct snd_pcm_runtime *runtime, unsigned int cond, + int var, + snd_pcm_hw_rule_func_t func, void *private, + int dep, ...) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_pcm_hw_rule *c; + unsigned int k; + va_list args; + va_start(args, dep); + if (constrs->rules_num >= constrs->rules_all) { + struct snd_pcm_hw_rule *new; + unsigned int new_rules = constrs->rules_all + 16; + new = krealloc_array(constrs->rules, new_rules, + sizeof(*c), GFP_KERNEL); + if (!new) { + va_end(args); + return -ENOMEM; + } + constrs->rules = new; + constrs->rules_all = new_rules; + } + c = &constrs->rules[constrs->rules_num]; + c->cond = cond; + c->func = func; + c->var = var; + c->private = private; + k = 0; + while (1) { + if (snd_BUG_ON(k >= ARRAY_SIZE(c->deps))) { + va_end(args); + return -EINVAL; + } + c->deps[k++] = dep; + if (dep < 0) + break; + dep = va_arg(args, int); + } + constrs->rules_num++; + va_end(args); + return 0; +} +EXPORT_SYMBOL(snd_pcm_hw_rule_add); + +/** + * snd_pcm_hw_constraint_mask - apply the given bitmap mask constraint + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the mask + * @mask: the bitmap mask + * + * Apply the constraint of the given bitmap mask to a 32-bit mask parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var, + u_int32_t mask) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_mask *maskp = constrs_mask(constrs, var); + *maskp->bits &= mask; + memset(maskp->bits + 1, 0, (SNDRV_MASK_MAX-32) / 8); /* clear rest */ + if (*maskp->bits == 0) + return -EINVAL; + return 0; +} + +/** + * snd_pcm_hw_constraint_mask64 - apply the given bitmap mask constraint + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the mask + * @mask: the 64bit bitmap mask + * + * Apply the constraint of the given bitmap mask to a 64-bit mask parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_mask64(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var, + u_int64_t mask) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_mask *maskp = constrs_mask(constrs, var); + maskp->bits[0] &= (u_int32_t)mask; + maskp->bits[1] &= (u_int32_t)(mask >> 32); + memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX-64) / 8); /* clear rest */ + if (! maskp->bits[0] && ! maskp->bits[1]) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_mask64); + +/** + * snd_pcm_hw_constraint_integer - apply an integer constraint to an interval + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the integer constraint + * + * Apply the constraint of integer to an interval parameter. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_pcm_hw_constraint_integer(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + return snd_interval_setinteger(constrs_interval(constrs, var)); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_integer); + +/** + * snd_pcm_hw_constraint_minmax - apply a min/max range constraint to an interval + * @runtime: PCM runtime instance + * @var: hw_params variable to apply the range + * @min: the minimal value + * @max: the maximal value + * + * Apply the min/max range constraint to an interval parameter. + * + * Return: Positive if the value is changed, zero if it's not changed, or a + * negative error code. + */ +int snd_pcm_hw_constraint_minmax(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var, + unsigned int min, unsigned int max) +{ + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + struct snd_interval t; + t.min = min; + t.max = max; + t.openmin = t.openmax = 0; + t.integer = 0; + return snd_interval_refine(constrs_interval(constrs, var), &t); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_minmax); + +static int snd_pcm_hw_rule_list(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_hw_constraint_list *list = rule->private; + return snd_interval_list(hw_param_interval(params, rule->var), list->count, list->list, list->mask); +} + + +/** + * snd_pcm_hw_constraint_list - apply a list of constraints to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the list constraint + * @l: list + * + * Apply the list of constraints to an interval parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_list(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_list *l) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_list, (void *)l, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_list); + +static int snd_pcm_hw_rule_ranges(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_hw_constraint_ranges *r = rule->private; + return snd_interval_ranges(hw_param_interval(params, rule->var), + r->count, r->ranges, r->mask); +} + + +/** + * snd_pcm_hw_constraint_ranges - apply list of range constraints to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the list of range constraints + * @r: ranges + * + * Apply the list of range constraints to an interval parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_ranges(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_ranges *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ranges, (void *)r, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_ranges); + +static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_pcm_hw_constraint_ratnums *r = rule->private; + unsigned int num = 0, den = 0; + int err; + err = snd_interval_ratnum(hw_param_interval(params, rule->var), + r->nrats, r->rats, &num, &den); + if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + return err; +} + +/** + * snd_pcm_hw_constraint_ratnums - apply ratnums constraint to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the ratnums constraint + * @r: struct snd_ratnums constriants + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_ratnums *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ratnums, (void *)r, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_ratnums); + +static int snd_pcm_hw_rule_ratdens(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_pcm_hw_constraint_ratdens *r = rule->private; + unsigned int num = 0, den = 0; + int err = snd_interval_ratden(hw_param_interval(params, rule->var), + r->nrats, r->rats, &num, &den); + if (err >= 0 && den && rule->var == SNDRV_PCM_HW_PARAM_RATE) { + params->rate_num = num; + params->rate_den = den; + } + return err; +} + +/** + * snd_pcm_hw_constraint_ratdens - apply ratdens constraint to a parameter + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the ratdens constraint + * @r: struct snd_ratdens constriants + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_ratdens(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + const struct snd_pcm_hw_constraint_ratdens *r) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_ratdens, (void *)r, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_ratdens); + +static int snd_pcm_hw_rule_msbits(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int l = (unsigned long) rule->private; + int width = l & 0xffff; + unsigned int msbits = l >> 16; + const struct snd_interval *i = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + + if (!snd_interval_single(i)) + return 0; + + if ((snd_interval_value(i) == width) || + (width == 0 && snd_interval_value(i) > msbits)) + params->msbits = min_not_zero(params->msbits, msbits); + + return 0; +} + +/** + * snd_pcm_hw_constraint_msbits - add a hw constraint msbits rule + * @runtime: PCM runtime instance + * @cond: condition bits + * @width: sample bits width + * @msbits: msbits width + * + * This constraint will set the number of most significant bits (msbits) if a + * sample format with the specified width has been select. If width is set to 0 + * the msbits will be set for any sample format with a width larger than the + * specified msbits. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_msbits(struct snd_pcm_runtime *runtime, + unsigned int cond, + unsigned int width, + unsigned int msbits) +{ + unsigned long l = (msbits << 16) | width; + return snd_pcm_hw_rule_add(runtime, cond, -1, + snd_pcm_hw_rule_msbits, + (void*) l, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_msbits); + +static int snd_pcm_hw_rule_step(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned long step = (unsigned long) rule->private; + return snd_interval_step(hw_param_interval(params, rule->var), step); +} + +/** + * snd_pcm_hw_constraint_step - add a hw constraint step rule + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the step constraint + * @step: step size + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_step(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var, + unsigned long step) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_step, (void *) step, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_step); + +static int snd_pcm_hw_rule_pow2(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + static const unsigned int pow2_sizes[] = { + 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, + 1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15, + 1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23, + 1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30 + }; + return snd_interval_list(hw_param_interval(params, rule->var), + ARRAY_SIZE(pow2_sizes), pow2_sizes, 0); +} + +/** + * snd_pcm_hw_constraint_pow2 - add a hw constraint power-of-2 rule + * @runtime: PCM runtime instance + * @cond: condition bits + * @var: hw_params variable to apply the power-of-2 constraint + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_constraint_pow2(struct snd_pcm_runtime *runtime, + unsigned int cond, + snd_pcm_hw_param_t var) +{ + return snd_pcm_hw_rule_add(runtime, cond, var, + snd_pcm_hw_rule_pow2, NULL, + var, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_constraint_pow2); + +static int snd_pcm_hw_rule_noresample_func(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + unsigned int base_rate = (unsigned int)(uintptr_t)rule->private; + struct snd_interval *rate; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + return snd_interval_list(rate, 1, &base_rate, 0); +} + +/** + * snd_pcm_hw_rule_noresample - add a rule to allow disabling hw resampling + * @runtime: PCM runtime instance + * @base_rate: the rate at which the hardware does not resample + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_hw_rule_noresample(struct snd_pcm_runtime *runtime, + unsigned int base_rate) +{ + return snd_pcm_hw_rule_add(runtime, SNDRV_PCM_HW_PARAMS_NORESAMPLE, + SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_noresample_func, + (void *)(uintptr_t)base_rate, + SNDRV_PCM_HW_PARAM_RATE, -1); +} +EXPORT_SYMBOL(snd_pcm_hw_rule_noresample); + +static void _snd_pcm_hw_param_any(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + if (hw_is_mask(var)) { + snd_mask_any(hw_param_mask(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + return; + } + if (hw_is_interval(var)) { + snd_interval_any(hw_param_interval(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + return; + } + snd_BUG(); +} + +void _snd_pcm_hw_params_any(struct snd_pcm_hw_params *params) +{ + unsigned int k; + memset(params, 0, sizeof(*params)); + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) + _snd_pcm_hw_param_any(params, k); + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) + _snd_pcm_hw_param_any(params, k); + params->info = ~0U; +} +EXPORT_SYMBOL(_snd_pcm_hw_params_any); + +/** + * snd_pcm_hw_param_value - return @params field @var value + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or %NULL + * + * Return: The value for field @var if it's fixed in configuration space + * defined by @params. -%EINVAL otherwise. + */ +int snd_pcm_hw_param_value(const struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + if (hw_is_mask(var)) { + const struct snd_mask *mask = hw_param_mask_c(params, var); + if (!snd_mask_single(mask)) + return -EINVAL; + if (dir) + *dir = 0; + return snd_mask_value(mask); + } + if (hw_is_interval(var)) { + const struct snd_interval *i = hw_param_interval_c(params, var); + if (!snd_interval_single(i)) + return -EINVAL; + if (dir) + *dir = i->openmin; + return snd_interval_value(i); + } + return -EINVAL; +} +EXPORT_SYMBOL(snd_pcm_hw_param_value); + +void _snd_pcm_hw_param_setempty(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + if (hw_is_mask(var)) { + snd_mask_none(hw_param_mask(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } else if (hw_is_interval(var)) { + snd_interval_none(hw_param_interval(params, var)); + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } else { + snd_BUG(); + } +} +EXPORT_SYMBOL(_snd_pcm_hw_param_setempty); + +static int _snd_pcm_hw_param_first(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + int changed; + if (hw_is_mask(var)) + changed = snd_mask_refine_first(hw_param_mask(params, var)); + else if (hw_is_interval(var)) + changed = snd_interval_refine_first(hw_param_interval(params, var)); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + + +/** + * snd_pcm_hw_param_first - refine config space and return minimum value + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or %NULL + * + * Inside configuration space defined by @params remove from @var all + * values > minimum. Reduce configuration space accordingly. + * + * Return: The minimum, or a negative error code on failure. + */ +int snd_pcm_hw_param_first(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + int changed = _snd_pcm_hw_param_first(params, var); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value(params, var, dir); +} +EXPORT_SYMBOL(snd_pcm_hw_param_first); + +static int _snd_pcm_hw_param_last(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var) +{ + int changed; + if (hw_is_mask(var)) + changed = snd_mask_refine_last(hw_param_mask(params, var)); + else if (hw_is_interval(var)) + changed = snd_interval_refine_last(hw_param_interval(params, var)); + else + return -EINVAL; + if (changed > 0) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} + + +/** + * snd_pcm_hw_param_last - refine config space and return maximum value + * @pcm: PCM instance + * @params: the hw_params instance + * @var: parameter to retrieve + * @dir: pointer to the direction (-1,0,1) or %NULL + * + * Inside configuration space defined by @params remove from @var all + * values < maximum. Reduce configuration space accordingly. + * + * Return: The maximum, or a negative error code on failure. + */ +int snd_pcm_hw_param_last(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, int *dir) +{ + int changed = _snd_pcm_hw_param_last(params, var); + if (changed < 0) + return changed; + if (params->rmask) { + int err = snd_pcm_hw_refine(pcm, params); + if (err < 0) + return err; + } + return snd_pcm_hw_param_value(params, var, dir); +} +EXPORT_SYMBOL(snd_pcm_hw_param_last); + +static int snd_pcm_lib_ioctl_reset(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + snd_pcm_stream_lock_irqsave(substream, flags); + if (snd_pcm_running(substream) && + snd_pcm_update_hw_ptr(substream) >= 0) + runtime->status->hw_ptr %= runtime->buffer_size; + else { + runtime->status->hw_ptr = 0; + runtime->hw_ptr_wrap = 0; + } + snd_pcm_stream_unlock_irqrestore(substream, flags); + return 0; +} + +static int snd_pcm_lib_ioctl_channel_info(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_channel_info *info = arg; + struct snd_pcm_runtime *runtime = substream->runtime; + int width; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) { + info->offset = -1; + return 0; + } + width = snd_pcm_format_physical_width(runtime->format); + if (width < 0) + return width; + info->offset = 0; + switch (runtime->access) { + case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED: + case SNDRV_PCM_ACCESS_RW_INTERLEAVED: + info->first = info->channel * width; + info->step = runtime->channels * width; + break; + case SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED: + case SNDRV_PCM_ACCESS_RW_NONINTERLEAVED: + { + size_t size = runtime->dma_bytes / runtime->channels; + info->first = info->channel * size * 8; + info->step = width; + break; + } + default: + snd_BUG(); + break; + } + return 0; +} + +static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_hw_params *params = arg; + snd_pcm_format_t format; + int channels; + ssize_t frame_size; + + params->fifo_size = substream->runtime->hw.fifo_size; + if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_FIFO_IN_FRAMES)) { + format = params_format(params); + channels = params_channels(params); + frame_size = snd_pcm_format_size(format, channels); + if (frame_size > 0) + params->fifo_size /= frame_size; + } + return 0; +} + +/** + * snd_pcm_lib_ioctl - a generic PCM ioctl callback + * @substream: the pcm substream instance + * @cmd: ioctl command + * @arg: ioctl argument + * + * Processes the generic ioctl commands for PCM. + * Can be passed as the ioctl callback for PCM ops. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + return snd_pcm_lib_ioctl_reset(substream, arg); + case SNDRV_PCM_IOCTL1_CHANNEL_INFO: + return snd_pcm_lib_ioctl_channel_info(substream, arg); + case SNDRV_PCM_IOCTL1_FIFO_SIZE: + return snd_pcm_lib_ioctl_fifo_size(substream, arg); + } + return -ENXIO; +} +EXPORT_SYMBOL(snd_pcm_lib_ioctl); + +/** + * snd_pcm_period_elapsed_under_stream_lock() - update the status of runtime for the next period + * under acquired lock of PCM substream. + * @substream: the instance of pcm substream. + * + * This function is called when the batch of audio data frames as the same size as the period of + * buffer is already processed in audio data transmission. + * + * The call of function updates the status of runtime with the latest position of audio data + * transmission, checks overrun and underrun over buffer, awaken user processes from waiting for + * available audio data frames, sampling audio timestamp, and performs stop or drain the PCM + * substream according to configured threshold. + * + * The function is intended to use for the case that PCM driver operates audio data frames under + * acquired lock of PCM substream; e.g. in callback of any operation of &snd_pcm_ops in process + * context. In any interrupt context, it's preferrable to use ``snd_pcm_period_elapsed()`` instead + * since lock of PCM substream should be acquired in advance. + * + * Developer should pay enough attention that some callbacks in &snd_pcm_ops are done by the call of + * function: + * + * - .pointer - to retrieve current position of audio data transmission by frame count or XRUN state. + * - .trigger - with SNDRV_PCM_TRIGGER_STOP at XRUN or DRAINING state. + * - .get_time_info - to retrieve audio time stamp if needed. + * + * Even if more than one periods have elapsed since the last call, you have to call this only once. + */ +void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return; + runtime = substream->runtime; + + if (!snd_pcm_running(substream) || + snd_pcm_update_hw_ptr0(substream, 1) < 0) + goto _end; + +#ifdef CONFIG_SND_PCM_TIMER + if (substream->timer_running) + snd_timer_interrupt(substream->timer, 1); +#endif + _end: + snd_kill_fasync(runtime->fasync, SIGIO, POLL_IN); +} +EXPORT_SYMBOL(snd_pcm_period_elapsed_under_stream_lock); + +/** + * snd_pcm_period_elapsed() - update the status of runtime for the next period by acquiring lock of + * PCM substream. + * @substream: the instance of PCM substream. + * + * This function is mostly similar to ``snd_pcm_period_elapsed_under_stream_lock()`` except for + * acquiring lock of PCM substream voluntarily. + * + * It's typically called by any type of IRQ handler when hardware IRQ occurs to notify event that + * the batch of audio data frames as the same size as the period of buffer is already processed in + * audio data transmission. + */ +void snd_pcm_period_elapsed(struct snd_pcm_substream *substream) +{ + unsigned long flags; + + if (snd_BUG_ON(!substream)) + return; + + snd_pcm_stream_lock_irqsave(substream, flags); + snd_pcm_period_elapsed_under_stream_lock(substream); + snd_pcm_stream_unlock_irqrestore(substream, flags); +} +EXPORT_SYMBOL(snd_pcm_period_elapsed); + +/* + * Wait until avail_min data becomes available + * Returns a negative error code if any error occurs during operation. + * The available space is stored on availp. When err = 0 and avail = 0 + * on the capture stream, it indicates the stream is in DRAINING state. + */ +static int wait_for_avail(struct snd_pcm_substream *substream, + snd_pcm_uframes_t *availp) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + wait_queue_entry_t wait; + int err = 0; + snd_pcm_uframes_t avail = 0; + long wait_time, tout; + + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&runtime->tsleep, &wait); + + if (runtime->no_period_wakeup) + wait_time = MAX_SCHEDULE_TIMEOUT; + else { + /* use wait time from substream if available */ + if (substream->wait_time) { + wait_time = substream->wait_time; + } else { + wait_time = 100; + + if (runtime->rate) { + long t = runtime->buffer_size * 1100 / runtime->rate; + wait_time = max(t, wait_time); + } + } + wait_time = msecs_to_jiffies(wait_time); + } + + for (;;) { + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + + /* + * We need to check if space became available already + * (and thus the wakeup happened already) first to close + * the race of space already having become available. + * This check must happen after been added to the waitqueue + * and having current state be INTERRUPTIBLE. + */ + avail = snd_pcm_avail(substream); + if (avail >= runtime->twake) + break; + snd_pcm_stream_unlock_irq(substream); + + tout = schedule_timeout(wait_time); + + snd_pcm_stream_lock_irq(substream); + set_current_state(TASK_INTERRUPTIBLE); + switch (runtime->state) { + case SNDRV_PCM_STATE_SUSPENDED: + err = -ESTRPIPE; + goto _endloop; + case SNDRV_PCM_STATE_XRUN: + err = -EPIPE; + goto _endloop; + case SNDRV_PCM_STATE_DRAINING: + if (is_playback) + err = -EPIPE; + else + avail = 0; /* indicate draining */ + goto _endloop; + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_DISCONNECTED: + err = -EBADFD; + goto _endloop; + case SNDRV_PCM_STATE_PAUSED: + continue; + } + if (!tout) { + pcm_dbg(substream->pcm, + "%s timeout (DMA or IRQ trouble?)\n", + is_playback ? "playback write" : "capture read"); + err = -EIO; + break; + } + } + _endloop: + set_current_state(TASK_RUNNING); + remove_wait_queue(&runtime->tsleep, &wait); + *availp = avail; + return err; +} + +typedef int (*pcm_transfer_f)(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + struct iov_iter *iter, unsigned long bytes); + +typedef int (*pcm_copy_f)(struct snd_pcm_substream *, snd_pcm_uframes_t, void *, + snd_pcm_uframes_t, snd_pcm_uframes_t, pcm_transfer_f, + bool); + +/* calculate the target DMA-buffer position to be written/read */ +static void *get_dma_ptr(struct snd_pcm_runtime *runtime, + int channel, unsigned long hwoff) +{ + return runtime->dma_area + hwoff + + channel * (runtime->dma_bytes / runtime->channels); +} + +/* default copy ops for write; used for both interleaved and non- modes */ +static int default_write_copy(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + struct iov_iter *iter, unsigned long bytes) +{ + if (copy_from_iter(get_dma_ptr(substream->runtime, channel, hwoff), + bytes, iter) != bytes) + return -EFAULT; + return 0; +} + +/* fill silence instead of copy data; called as a transfer helper + * from __snd_pcm_lib_write() or directly from noninterleaved_copy() when + * a NULL buffer is passed + */ +static int fill_silence(struct snd_pcm_substream *substream, int channel, + unsigned long hwoff, struct iov_iter *iter, + unsigned long bytes) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + if (substream->ops->fill_silence) + return substream->ops->fill_silence(substream, channel, + hwoff, bytes); + + snd_pcm_format_set_silence(runtime->format, + get_dma_ptr(runtime, channel, hwoff), + bytes_to_samples(runtime, bytes)); + return 0; +} + +/* default copy ops for read; used for both interleaved and non- modes */ +static int default_read_copy(struct snd_pcm_substream *substream, + int channel, unsigned long hwoff, + struct iov_iter *iter, unsigned long bytes) +{ + if (copy_to_iter(get_dma_ptr(substream->runtime, channel, hwoff), + bytes, iter) != bytes) + return -EFAULT; + return 0; +} + +/* call transfer with the filled iov_iter */ +static int do_transfer(struct snd_pcm_substream *substream, int c, + unsigned long hwoff, void *data, unsigned long bytes, + pcm_transfer_f transfer, bool in_kernel) +{ + struct iov_iter iter; + int err, type; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + type = ITER_SOURCE; + else + type = ITER_DEST; + + if (in_kernel) { + struct kvec kvec = { data, bytes }; + + iov_iter_kvec(&iter, type, &kvec, 1, bytes); + return transfer(substream, c, hwoff, &iter, bytes); + } + + err = import_ubuf(type, (__force void __user *)data, bytes, &iter); + if (err) + return err; + return transfer(substream, c, hwoff, &iter, bytes); +} + +/* call transfer function with the converted pointers and sizes; + * for interleaved mode, it's one shot for all samples + */ +static int interleaved_copy(struct snd_pcm_substream *substream, + snd_pcm_uframes_t hwoff, void *data, + snd_pcm_uframes_t off, + snd_pcm_uframes_t frames, + pcm_transfer_f transfer, + bool in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + /* convert to bytes */ + hwoff = frames_to_bytes(runtime, hwoff); + off = frames_to_bytes(runtime, off); + frames = frames_to_bytes(runtime, frames); + + return do_transfer(substream, 0, hwoff, data + off, frames, transfer, + in_kernel); +} + +/* call transfer function with the converted pointers and sizes for each + * non-interleaved channel; when buffer is NULL, silencing instead of copying + */ +static int noninterleaved_copy(struct snd_pcm_substream *substream, + snd_pcm_uframes_t hwoff, void *data, + snd_pcm_uframes_t off, + snd_pcm_uframes_t frames, + pcm_transfer_f transfer, + bool in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int channels = runtime->channels; + void **bufs = data; + int c, err; + + /* convert to bytes; note that it's not frames_to_bytes() here. + * in non-interleaved mode, we copy for each channel, thus + * each copy is n_samples bytes x channels = whole frames. + */ + off = samples_to_bytes(runtime, off); + frames = samples_to_bytes(runtime, frames); + hwoff = samples_to_bytes(runtime, hwoff); + for (c = 0; c < channels; ++c, ++bufs) { + if (!data || !*bufs) + err = fill_silence(substream, c, hwoff, NULL, frames); + else + err = do_transfer(substream, c, hwoff, *bufs + off, + frames, transfer, in_kernel); + if (err < 0) + return err; + } + return 0; +} + +/* fill silence on the given buffer position; + * called from snd_pcm_playback_silence() + */ +static int fill_silence_frames(struct snd_pcm_substream *substream, + snd_pcm_uframes_t off, snd_pcm_uframes_t frames) +{ + if (substream->runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + substream->runtime->access == SNDRV_PCM_ACCESS_MMAP_INTERLEAVED) + return interleaved_copy(substream, off, NULL, 0, frames, + fill_silence, true); + else + return noninterleaved_copy(substream, off, NULL, 0, frames, + fill_silence, true); +} + +/* sanity-check for read/write methods */ +static int pcm_sanity_check(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (snd_BUG_ON(!substream->ops->copy && !runtime->dma_area)) + return -EINVAL; + if (runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + return 0; +} + +static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +{ + switch (runtime->state) { + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PAUSED: + return 0; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + case SNDRV_PCM_STATE_SUSPENDED: + return -ESTRPIPE; + default: + return -EBADFD; + } +} + +/* update to the given appl_ptr and call ack callback if needed; + * when an error is returned, take back to the original value + */ +int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t appl_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t old_appl_ptr = runtime->control->appl_ptr; + snd_pcm_sframes_t diff; + int ret; + + if (old_appl_ptr == appl_ptr) + return 0; + + if (appl_ptr >= runtime->boundary) + return -EINVAL; + /* + * check if a rewind is requested by the application + */ + if (substream->runtime->info & SNDRV_PCM_INFO_NO_REWINDS) { + diff = appl_ptr - old_appl_ptr; + if (diff >= 0) { + if (diff > runtime->buffer_size) + return -EINVAL; + } else { + if (runtime->boundary + diff > runtime->buffer_size) + return -EINVAL; + } + } + + runtime->control->appl_ptr = appl_ptr; + if (substream->ops->ack) { + ret = substream->ops->ack(substream); + if (ret < 0) { + runtime->control->appl_ptr = old_appl_ptr; + if (ret == -EPIPE) + __snd_pcm_xrun(substream); + return ret; + } + } + + trace_applptr(substream, old_appl_ptr, appl_ptr); + + return 0; +} + +/* the common loop for read/write data */ +snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, + void *data, bool interleaved, + snd_pcm_uframes_t size, bool in_kernel) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t xfer = 0; + snd_pcm_uframes_t offset = 0; + snd_pcm_uframes_t avail; + pcm_copy_f writer; + pcm_transfer_f transfer; + bool nonblock; + bool is_playback; + int err; + + err = pcm_sanity_check(substream); + if (err < 0) + return err; + + is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (interleaved) { + if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED && + runtime->channels > 1) + return -EINVAL; + writer = interleaved_copy; + } else { + if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + return -EINVAL; + writer = noninterleaved_copy; + } + + if (!data) { + if (is_playback) + transfer = fill_silence; + else + return -EINVAL; + } else { + if (substream->ops->copy) + transfer = substream->ops->copy; + else + transfer = is_playback ? + default_write_copy : default_read_copy; + } + + if (size == 0) + return 0; + + nonblock = !!(substream->f_flags & O_NONBLOCK); + + snd_pcm_stream_lock_irq(substream); + err = pcm_accessible_state(runtime); + if (err < 0) + goto _end_unlock; + + runtime->twake = runtime->control->avail_min ? : 1; + if (runtime->state == SNDRV_PCM_STATE_RUNNING) + snd_pcm_update_hw_ptr(substream); + + /* + * If size < start_threshold, wait indefinitely. Another + * thread may start capture + */ + if (!is_playback && + runtime->state == SNDRV_PCM_STATE_PREPARED && + size >= runtime->start_threshold) { + err = snd_pcm_start(substream); + if (err < 0) + goto _end_unlock; + } + + avail = snd_pcm_avail(substream); + + while (size > 0) { + snd_pcm_uframes_t frames, appl_ptr, appl_ofs; + snd_pcm_uframes_t cont; + if (!avail) { + if (!is_playback && + runtime->state == SNDRV_PCM_STATE_DRAINING) { + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + goto _end_unlock; + } + if (nonblock) { + err = -EAGAIN; + goto _end_unlock; + } + runtime->twake = min_t(snd_pcm_uframes_t, size, + runtime->control->avail_min ? : 1); + err = wait_for_avail(substream, &avail); + if (err < 0) + goto _end_unlock; + if (!avail) + continue; /* draining */ + } + frames = size > avail ? avail : size; + appl_ptr = READ_ONCE(runtime->control->appl_ptr); + appl_ofs = appl_ptr % runtime->buffer_size; + cont = runtime->buffer_size - appl_ofs; + if (frames > cont) + frames = cont; + if (snd_BUG_ON(!frames)) { + err = -EINVAL; + goto _end_unlock; + } + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) { + err = -EBUSY; + goto _end_unlock; + } + snd_pcm_stream_unlock_irq(substream); + if (!is_playback) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); + err = writer(substream, appl_ofs, data, offset, frames, + transfer, in_kernel); + if (is_playback) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + snd_pcm_stream_lock_irq(substream); + atomic_dec(&runtime->buffer_accessing); + if (err < 0) + goto _end_unlock; + err = pcm_accessible_state(runtime); + if (err < 0) + goto _end_unlock; + appl_ptr += frames; + if (appl_ptr >= runtime->boundary) + appl_ptr -= runtime->boundary; + err = pcm_lib_apply_appl_ptr(substream, appl_ptr); + if (err < 0) + goto _end_unlock; + + offset += frames; + size -= frames; + xfer += frames; + avail -= frames; + if (is_playback && + runtime->state == SNDRV_PCM_STATE_PREPARED && + snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) { + err = snd_pcm_start(substream); + if (err < 0) + goto _end_unlock; + } + } + _end_unlock: + runtime->twake = 0; + if (xfer > 0 && err >= 0) + snd_pcm_update_state(substream, runtime); + snd_pcm_stream_unlock_irq(substream); + return xfer > 0 ? (snd_pcm_sframes_t)xfer : err; +} +EXPORT_SYMBOL(__snd_pcm_lib_xfer); + +/* + * standard channel mapping helpers + */ + +/* default channel maps for multi-channel playbacks, up to 8 channels */ +const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { .channels = 8, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, + { } +}; +EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps); + +/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */ +const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = { + { .channels = 1, + .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 8, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, + { } +}; +EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps); + +static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch) +{ + if (ch > info->max_channels) + return false; + return !info->channel_mask || (info->channel_mask & (1U << ch)); +} + +static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = info->max_channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SNDRV_CHMAP_LAST; + return 0; +} + +/* get callback for channel map ctl element + * stores the channel position firstly matching with the current channels + */ +static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct snd_pcm_substream *substream; + const struct snd_pcm_chmap_elem *map; + + if (!info->chmap) + return -EINVAL; + substream = snd_pcm_chmap_substream(info, idx); + if (!substream) + return -ENODEV; + memset(ucontrol->value.integer.value, 0, + sizeof(long) * info->max_channels); + if (!substream->runtime) + return 0; /* no channels set */ + for (map = info->chmap; map->channels; map++) { + int i; + if (map->channels == substream->runtime->channels && + valid_chmap_channels(info, map->channels)) { + for (i = 0; i < map->channels; i++) + ucontrol->value.integer.value[i] = map->map[i]; + return 0; + } + } + return -EINVAL; +} + +/* tlv callback for channel map ctl element + * expands the pre-defined channel maps in a form of TLV + */ +static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + const struct snd_pcm_chmap_elem *map; + unsigned int __user *dst; + int c, count = 0; + + if (!info->chmap) + return -EINVAL; + if (size < 8) + return -ENOMEM; + if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) + return -EFAULT; + size -= 8; + dst = tlv + 2; + for (map = info->chmap; map->channels; map++) { + int chs_bytes = map->channels * 4; + if (!valid_chmap_channels(info, map->channels)) + continue; + if (size < 8) + return -ENOMEM; + if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) || + put_user(chs_bytes, dst + 1)) + return -EFAULT; + dst += 2; + size -= 8; + count += 8; + if (size < chs_bytes) + return -ENOMEM; + size -= chs_bytes; + count += chs_bytes; + for (c = 0; c < map->channels; c++) { + if (put_user(map->map[c], dst)) + return -EFAULT; + dst++; + } + } + if (put_user(count, tlv + 1)) + return -EFAULT; + return 0; +} + +static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol) +{ + struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); + info->pcm->streams[info->stream].chmap_kctl = NULL; + kfree(info); +} + +/** + * snd_pcm_add_chmap_ctls - create channel-mapping control elements + * @pcm: the assigned PCM instance + * @stream: stream direction + * @chmap: channel map elements (for query) + * @max_channels: the max number of channels for the stream + * @private_value: the value passed to each kcontrol's private_value field + * @info_ret: store struct snd_pcm_chmap instance if non-NULL + * + * Create channel-mapping control elements assigned to the given PCM stream(s). + * Return: Zero if successful, or a negative error value. + */ +int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, + const struct snd_pcm_chmap_elem *chmap, + int max_channels, + unsigned long private_value, + struct snd_pcm_chmap **info_ret) +{ + struct snd_pcm_chmap *info; + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, + .info = pcm_chmap_ctl_info, + .get = pcm_chmap_ctl_get, + .tlv.c = pcm_chmap_ctl_tlv, + }; + int err; + + if (WARN_ON(pcm->streams[stream].chmap_kctl)) + return -EBUSY; + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->pcm = pcm; + info->stream = stream; + info->chmap = chmap; + info->max_channels = max_channels; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + knew.name = "Playback Channel Map"; + else + knew.name = "Capture Channel Map"; + knew.device = pcm->device; + knew.count = pcm->streams[stream].substream_count; + knew.private_value = private_value; + info->kctl = snd_ctl_new1(&knew, info); + if (!info->kctl) { + kfree(info); + return -ENOMEM; + } + info->kctl->private_free = pcm_chmap_ctl_private_free; + err = snd_ctl_add(pcm->card, info->kctl); + if (err < 0) + return err; + pcm->streams[stream].chmap_kctl = info->kctl; + if (info_ret) + *info_ret = info; + return 0; +} +EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls); diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h new file mode 100644 index 0000000000..ecb21697ae --- /dev/null +++ b/sound/core/pcm_local.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * pcm_local.h - a local header file for snd-pcm module. + * + * Copyright (c) Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#ifndef __SOUND_CORE_PCM_LOCAL_H +#define __SOUND_CORE_PCM_LOCAL_H + +extern const struct snd_pcm_hw_constraint_list snd_pcm_known_rates; + +void snd_interval_mul(const struct snd_interval *a, + const struct snd_interval *b, struct snd_interval *c); +void snd_interval_div(const struct snd_interval *a, + const struct snd_interval *b, struct snd_interval *c); +void snd_interval_muldivk(const struct snd_interval *a, + const struct snd_interval *b, + unsigned int k, struct snd_interval *c); +void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k, + const struct snd_interval *b, struct snd_interval *c); + +int snd_pcm_hw_constraint_mask(struct snd_pcm_runtime *runtime, + snd_pcm_hw_param_t var, u_int32_t mask); + +int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t appl_ptr); +int snd_pcm_update_state(struct snd_pcm_substream *substream, + struct snd_pcm_runtime *runtime); +int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream); + +void snd_pcm_playback_silence(struct snd_pcm_substream *substream, + snd_pcm_uframes_t new_hw_ptr); + +static inline snd_pcm_uframes_t +snd_pcm_avail(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return snd_pcm_playback_avail(substream->runtime); + else + return snd_pcm_capture_avail(substream->runtime); +} + +static inline snd_pcm_uframes_t +snd_pcm_hw_avail(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return snd_pcm_playback_hw_avail(substream->runtime); + else + return snd_pcm_capture_hw_avail(substream->runtime); +} + +#ifdef CONFIG_SND_PCM_TIMER +void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream); +void snd_pcm_timer_init(struct snd_pcm_substream *substream); +void snd_pcm_timer_done(struct snd_pcm_substream *substream); +#else +static inline void +snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream) {} +static inline void snd_pcm_timer_init(struct snd_pcm_substream *substream) {} +static inline void snd_pcm_timer_done(struct snd_pcm_substream *substream) {} +#endif + +void __snd_pcm_xrun(struct snd_pcm_substream *substream); +void snd_pcm_group_init(struct snd_pcm_group *group); +void snd_pcm_sync_stop(struct snd_pcm_substream *substream, bool sync_irq); + +#define PCM_RUNTIME_CHECK(sub) snd_BUG_ON(!(sub) || !(sub)->runtime) + +/* loop over all PCM substreams */ +#define for_each_pcm_substream(pcm, str, subs) \ + for ((str) = 0; (str) < 2; (str)++) \ + for ((subs) = (pcm)->streams[str].substream; (subs); \ + (subs) = (subs)->next) + +static inline void snd_pcm_dma_buffer_sync(struct snd_pcm_substream *substream, + enum snd_dma_sync_mode mode) +{ + if (substream->runtime->info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), mode); +} + +#endif /* __SOUND_CORE_PCM_LOCAL_H */ diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c new file mode 100644 index 0000000000..a0b9514716 --- /dev/null +++ b/sound/core/pcm_memory.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/io.h> +#include <linux/time.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/moduleparam.h> +#include <linux/vmalloc.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/initval.h> +#include "pcm_local.h" + +static int preallocate_dma = 1; +module_param(preallocate_dma, int, 0444); +MODULE_PARM_DESC(preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized."); + +static int maximum_substreams = 4; +module_param(maximum_substreams, int, 0444); +MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA memory."); + +static const size_t snd_minimum_buffer = 16384; + +static unsigned long max_alloc_per_card = 32UL * 1024UL * 1024UL; +module_param(max_alloc_per_card, ulong, 0644); +MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card."); + +static void __update_allocated_size(struct snd_card *card, ssize_t bytes) +{ + card->total_pcm_alloc_bytes += bytes; +} + +static void update_allocated_size(struct snd_card *card, ssize_t bytes) +{ + mutex_lock(&card->memory_mutex); + __update_allocated_size(card, bytes); + mutex_unlock(&card->memory_mutex); +} + +static void decrease_allocated_size(struct snd_card *card, size_t bytes) +{ + mutex_lock(&card->memory_mutex); + WARN_ON(card->total_pcm_alloc_bytes < bytes); + __update_allocated_size(card, -(ssize_t)bytes); + mutex_unlock(&card->memory_mutex); +} + +static int do_alloc_pages(struct snd_card *card, int type, struct device *dev, + int str, size_t size, struct snd_dma_buffer *dmab) +{ + enum dma_data_direction dir; + int err; + + /* check and reserve the requested size */ + mutex_lock(&card->memory_mutex); + if (max_alloc_per_card && + card->total_pcm_alloc_bytes + size > max_alloc_per_card) { + mutex_unlock(&card->memory_mutex); + return -ENOMEM; + } + __update_allocated_size(card, size); + mutex_unlock(&card->memory_mutex); + + if (str == SNDRV_PCM_STREAM_PLAYBACK) + dir = DMA_TO_DEVICE; + else + dir = DMA_FROM_DEVICE; + err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab); + if (!err) { + /* the actual allocation size might be bigger than requested, + * and we need to correct the account + */ + if (dmab->bytes != size) + update_allocated_size(card, dmab->bytes - size); + } else { + /* take back on allocation failure */ + decrease_allocated_size(card, size); + } + return err; +} + +static void do_free_pages(struct snd_card *card, struct snd_dma_buffer *dmab) +{ + if (!dmab->area) + return; + decrease_allocated_size(card, dmab->bytes); + snd_dma_free_pages(dmab); + dmab->area = NULL; +} + +/* + * try to allocate as the large pages as possible. + * stores the resultant memory size in *res_size. + * + * the minimum size is snd_minimum_buffer. it should be power of 2. + */ +static int preallocate_pcm_pages(struct snd_pcm_substream *substream, + size_t size, bool no_fallback) +{ + struct snd_dma_buffer *dmab = &substream->dma_buffer; + struct snd_card *card = substream->pcm->card; + size_t orig_size = size; + int err; + + do { + err = do_alloc_pages(card, dmab->dev.type, dmab->dev.dev, + substream->stream, size, dmab); + if (err != -ENOMEM) + return err; + if (no_fallback) + break; + size >>= 1; + } while (size >= snd_minimum_buffer); + dmab->bytes = 0; /* tell error */ + pr_warn("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n", + substream->pcm->card->number, substream->pcm->device, + substream->stream ? 'c' : 'p', substream->number, + substream->pcm->name, orig_size); + return -ENOMEM; +} + +/** + * snd_pcm_lib_preallocate_free - release the preallocated buffer of the specified substream. + * @substream: the pcm substream instance + * + * Releases the pre-allocated buffer of the given substream. + */ +void snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream) +{ + do_free_pages(substream->pcm->card, &substream->dma_buffer); +} + +/** + * snd_pcm_lib_preallocate_free_for_all - release all pre-allocated buffers on the pcm + * @pcm: the pcm instance + * + * Releases all the pre-allocated buffers on the given pcm. + */ +void snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int stream; + + for_each_pcm_substream(pcm, stream, substream) + snd_pcm_lib_preallocate_free(substream); +} +EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all); + +#ifdef CONFIG_SND_VERBOSE_PROCFS +/* + * read callback for prealloc proc file + * + * prints the current allocated size in kB. + */ +static void snd_pcm_lib_preallocate_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_buffer.bytes / 1024); +} + +/* + * read callback for prealloc_max proc file + * + * prints the maximum allowed size in kB. + */ +static void snd_pcm_lib_preallocate_max_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_max / 1024); +} + +/* + * write callback for prealloc proc file + * + * accepts the preallocation size in kB. + */ +static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_pcm_substream *substream = entry->private_data; + struct snd_card *card = substream->pcm->card; + char line[64], str[64]; + size_t size; + struct snd_dma_buffer new_dmab; + + mutex_lock(&substream->pcm->open_mutex); + if (substream->runtime) { + buffer->error = -EBUSY; + goto unlock; + } + if (!snd_info_get_line(buffer, line, sizeof(line))) { + snd_info_get_str(str, line, sizeof(str)); + size = simple_strtoul(str, NULL, 10) * 1024; + if ((size != 0 && size < 8192) || size > substream->dma_max) { + buffer->error = -EINVAL; + goto unlock; + } + if (substream->dma_buffer.bytes == size) + goto unlock; + memset(&new_dmab, 0, sizeof(new_dmab)); + new_dmab.dev = substream->dma_buffer.dev; + if (size > 0) { + if (do_alloc_pages(card, + substream->dma_buffer.dev.type, + substream->dma_buffer.dev.dev, + substream->stream, + size, &new_dmab) < 0) { + buffer->error = -ENOMEM; + pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n", + substream->pcm->card->number, substream->pcm->device, + substream->stream ? 'c' : 'p', substream->number, + substream->pcm->name, size); + goto unlock; + } + substream->buffer_bytes_max = size; + } else { + substream->buffer_bytes_max = UINT_MAX; + } + if (substream->dma_buffer.area) + do_free_pages(card, &substream->dma_buffer); + substream->dma_buffer = new_dmab; + } else { + buffer->error = -EINVAL; + } + unlock: + mutex_unlock(&substream->pcm->open_mutex); +} + +static inline void preallocate_info_init(struct snd_pcm_substream *substream) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", + substream->proc_root); + if (entry) { + snd_info_set_text_ops(entry, substream, + snd_pcm_lib_preallocate_proc_read); + entry->c.text.write = snd_pcm_lib_preallocate_proc_write; + entry->mode |= 0200; + } + entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", + substream->proc_root); + if (entry) + snd_info_set_text_ops(entry, substream, + snd_pcm_lib_preallocate_max_proc_read); +} + +#else /* !CONFIG_SND_VERBOSE_PROCFS */ +static inline void preallocate_info_init(struct snd_pcm_substream *substream) +{ +} +#endif /* CONFIG_SND_VERBOSE_PROCFS */ + +/* + * pre-allocate the buffer and create a proc file for the substream + */ +static int preallocate_pages(struct snd_pcm_substream *substream, + int type, struct device *data, + size_t size, size_t max, bool managed) +{ + int err; + + if (snd_BUG_ON(substream->dma_buffer.dev.type)) + return -EINVAL; + + substream->dma_buffer.dev.type = type; + substream->dma_buffer.dev.dev = data; + + if (size > 0) { + if (!max) { + /* no fallback, only also inform -ENOMEM */ + err = preallocate_pcm_pages(substream, size, true); + if (err < 0) + return err; + } else if (preallocate_dma && + substream->number < maximum_substreams) { + err = preallocate_pcm_pages(substream, size, false); + if (err < 0 && err != -ENOMEM) + return err; + } + } + + if (substream->dma_buffer.bytes > 0) + substream->buffer_bytes_max = substream->dma_buffer.bytes; + substream->dma_max = max; + if (max > 0) + preallocate_info_init(substream); + if (managed) + substream->managed_buffer_alloc = 1; + return 0; +} + +static int preallocate_pages_for_all(struct snd_pcm *pcm, int type, + void *data, size_t size, size_t max, + bool managed) +{ + struct snd_pcm_substream *substream; + int stream, err; + + for_each_pcm_substream(pcm, stream, substream) { + err = preallocate_pages(substream, type, data, size, max, managed); + if (err < 0) + return err; + } + return 0; +} + +/** + * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type + * @substream: the pcm substream instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation for the given DMA buffer type. + */ +void snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream, + int type, struct device *data, + size_t size, size_t max) +{ + preallocate_pages(substream, type, data, size, max, false); +} +EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages); + +/** + * snd_pcm_lib_preallocate_pages_for_all - pre-allocation for continuous memory type (all substreams) + * @pcm: the pcm instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation to all substreams of the given pcm for the + * specified DMA type. + */ +void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, + int type, void *data, + size_t size, size_t max) +{ + preallocate_pages_for_all(pcm, type, data, size, max, false); +} +EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all); + +/** + * snd_pcm_set_managed_buffer - set up buffer management for a substream + * @substream: the pcm substream instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation for the given DMA buffer type, and set the managed + * buffer allocation mode to the given substream. + * In this mode, PCM core will allocate a buffer automatically before PCM + * hw_params ops call, and release the buffer after PCM hw_free ops call + * as well, so that the driver doesn't need to invoke the allocation and + * the release explicitly in its callback. + * When a buffer is actually allocated before the PCM hw_params call, it + * turns on the runtime buffer_changed flag for drivers changing their h/w + * parameters accordingly. + * + * When @size is non-zero and @max is zero, this tries to allocate for only + * the exact buffer size without fallback, and may return -ENOMEM. + * Otherwise, the function tries to allocate smaller chunks if the allocation + * fails. This is the behavior of snd_pcm_set_fixed_buffer(). + * + * When both @size and @max are zero, the function only sets up the buffer + * for later dynamic allocations. It's used typically for buffers with + * SNDRV_DMA_TYPE_VMALLOC type. + * + * Upon successful buffer allocation and setup, the function returns 0. + * + * Return: zero if successful, or a negative error code + */ +int snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type, + struct device *data, size_t size, size_t max) +{ + return preallocate_pages(substream, type, data, size, max, true); +} +EXPORT_SYMBOL(snd_pcm_set_managed_buffer); + +/** + * snd_pcm_set_managed_buffer_all - set up buffer management for all substreams + * for all substreams + * @pcm: the pcm instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation to all substreams of the given pcm for the specified DMA + * type and size, and set the managed_buffer_alloc flag to each substream. + * + * Return: zero if successful, or a negative error code + */ +int snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type, + struct device *data, + size_t size, size_t max) +{ + return preallocate_pages_for_all(pcm, type, data, size, max, true); +} +EXPORT_SYMBOL(snd_pcm_set_managed_buffer_all); + +/** + * snd_pcm_lib_malloc_pages - allocate the DMA buffer + * @substream: the substream to allocate the DMA buffer to + * @size: the requested buffer size in bytes + * + * Allocates the DMA buffer on the BUS type given earlier to + * snd_pcm_lib_preallocate_xxx_pages(). + * + * Return: 1 if the buffer is changed, 0 if not changed, or a negative + * code on failure. + */ +int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) +{ + struct snd_card *card; + struct snd_pcm_runtime *runtime; + struct snd_dma_buffer *dmab = NULL; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + if (snd_BUG_ON(substream->dma_buffer.dev.type == + SNDRV_DMA_TYPE_UNKNOWN)) + return -EINVAL; + runtime = substream->runtime; + card = substream->pcm->card; + + if (runtime->dma_buffer_p) { + /* perphaps, we might free the large DMA memory region + to save some space here, but the actual solution + costs us less time */ + if (runtime->dma_buffer_p->bytes >= size) { + runtime->dma_bytes = size; + return 0; /* ok, do not change */ + } + snd_pcm_lib_free_pages(substream); + } + if (substream->dma_buffer.area != NULL && + substream->dma_buffer.bytes >= size) { + dmab = &substream->dma_buffer; /* use the pre-allocated buffer */ + } else { + /* dma_max=0 means the fixed size preallocation */ + if (substream->dma_buffer.area && !substream->dma_max) + return -ENOMEM; + dmab = kzalloc(sizeof(*dmab), GFP_KERNEL); + if (! dmab) + return -ENOMEM; + dmab->dev = substream->dma_buffer.dev; + if (do_alloc_pages(card, + substream->dma_buffer.dev.type, + substream->dma_buffer.dev.dev, + substream->stream, + size, dmab) < 0) { + kfree(dmab); + pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n", + substream->pcm->card->number, substream->pcm->device, + substream->stream ? 'c' : 'p', substream->number, + substream->pcm->name, size); + return -ENOMEM; + } + } + snd_pcm_set_runtime_buffer(substream, dmab); + runtime->dma_bytes = size; + return 1; /* area was changed */ +} +EXPORT_SYMBOL(snd_pcm_lib_malloc_pages); + +/** + * snd_pcm_lib_free_pages - release the allocated DMA buffer. + * @substream: the substream to release the DMA buffer + * + * Releases the DMA buffer allocated via snd_pcm_lib_malloc_pages(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + runtime = substream->runtime; + if (runtime->dma_area == NULL) + return 0; + if (runtime->dma_buffer_p != &substream->dma_buffer) { + struct snd_card *card = substream->pcm->card; + + /* it's a newly allocated buffer. release it now. */ + do_free_pages(card, runtime->dma_buffer_p); + kfree(runtime->dma_buffer_p); + } + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} +EXPORT_SYMBOL(snd_pcm_lib_free_pages); + +int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream, + size_t size, gfp_t gfp_flags) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + runtime = substream->runtime; + if (runtime->dma_area) { + if (runtime->dma_bytes >= size) + return 0; /* already large enough */ + vfree(runtime->dma_area); + } + runtime->dma_area = __vmalloc(size, gfp_flags); + if (!runtime->dma_area) + return -ENOMEM; + runtime->dma_bytes = size; + return 1; +} +EXPORT_SYMBOL(_snd_pcm_lib_alloc_vmalloc_buffer); + +/** + * snd_pcm_lib_free_vmalloc_buffer - free vmalloc buffer + * @substream: the substream with a buffer allocated by + * snd_pcm_lib_alloc_vmalloc_buffer() + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + + if (PCM_RUNTIME_CHECK(substream)) + return -EINVAL; + runtime = substream->runtime; + vfree(runtime->dma_area); + runtime->dma_area = NULL; + return 0; +} +EXPORT_SYMBOL(snd_pcm_lib_free_vmalloc_buffer); + +/** + * snd_pcm_lib_get_vmalloc_page - map vmalloc buffer offset to page struct + * @substream: the substream with a buffer allocated by + * snd_pcm_lib_alloc_vmalloc_buffer() + * @offset: offset in the buffer + * + * This function is to be used as the page callback in the PCM ops. + * + * Return: The page struct, or %NULL on failure. + */ +struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + return vmalloc_to_page(substream->runtime->dma_area + offset); +} +EXPORT_SYMBOL(snd_pcm_lib_get_vmalloc_page); diff --git a/sound/core/pcm_misc.c b/sound/core/pcm_misc.c new file mode 100644 index 0000000000..5588b6a1ee --- /dev/null +++ b/sound/core/pcm_misc.c @@ -0,0 +1,616 @@ +/* + * PCM Interface - misc routines + * Copyright (c) 1998 by Jaroslav Kysela <perex@perex.cz> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/time.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/pcm.h> + +#include "pcm_local.h" + +#define SND_PCM_FORMAT_UNKNOWN (-1) + +/* NOTE: "signed" prefix must be given below since the default char is + * unsigned on some architectures! + */ +struct pcm_format_data { + unsigned char width; /* bit width */ + unsigned char phys; /* physical bit width */ + signed char le; /* 0 = big-endian, 1 = little-endian, -1 = others */ + signed char signd; /* 0 = unsigned, 1 = signed, -1 = others */ + unsigned char silence[8]; /* silence data to fill */ +}; + +/* we do lots of calculations on snd_pcm_format_t; shut up sparse */ +#define INT __force int + +static bool valid_format(snd_pcm_format_t format) +{ + return (INT)format >= 0 && (INT)format <= (INT)SNDRV_PCM_FORMAT_LAST; +} + +static const struct pcm_format_data pcm_formats[(INT)SNDRV_PCM_FORMAT_LAST+1] = { + [SNDRV_PCM_FORMAT_S8] = { + .width = 8, .phys = 8, .le = -1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U8] = { + .width = 8, .phys = 8, .le = -1, .signd = 0, + .silence = { 0x80 }, + }, + [SNDRV_PCM_FORMAT_S16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 0, + .silence = { 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 0, + .silence = { 0x80, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S24_LE] = { + .width = 24, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S24_BE] = { + .width = 24, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U24_LE] = { + .width = 24, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U24_BE] = { + .width = 24, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x00, 0x80, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x80, 0x00, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_FLOAT_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT64_LE] = { + .width = 64, .phys = 64, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_FLOAT64_BE] = { + .width = 64, .phys = 64, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_MU_LAW] = { + .width = 8, .phys = 8, .le = -1, .signd = -1, + .silence = { 0x7f }, + }, + [SNDRV_PCM_FORMAT_A_LAW] = { + .width = 8, .phys = 8, .le = -1, .signd = -1, + .silence = { 0x55 }, + }, + [SNDRV_PCM_FORMAT_IMA_ADPCM] = { + .width = 4, .phys = 4, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_G723_24] = { + .width = 3, .phys = 3, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_G723_40] = { + .width = 5, .phys = 5, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_DSD_U8] = { + .width = 8, .phys = 8, .le = 1, .signd = 0, + .silence = { 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U16_LE] = { + .width = 16, .phys = 16, .le = 1, .signd = 0, + .silence = { 0x69, 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U32_LE] = { + .width = 32, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x69, 0x69, 0x69, 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U16_BE] = { + .width = 16, .phys = 16, .le = 0, .signd = 0, + .silence = { 0x69, 0x69 }, + }, + [SNDRV_PCM_FORMAT_DSD_U32_BE] = { + .width = 32, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x69, 0x69, 0x69, 0x69 }, + }, + /* FIXME: the following two formats are not defined properly yet */ + [SNDRV_PCM_FORMAT_MPEG] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_GSM] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_S20_LE] = { + .width = 20, .phys = 32, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S20_BE] = { + .width = 20, .phys = 32, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U20_LE] = { + .width = 20, .phys = 32, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x08, 0x00 }, + }, + [SNDRV_PCM_FORMAT_U20_BE] = { + .width = 20, .phys = 32, .le = 0, .signd = 0, + .silence = { 0x00, 0x08, 0x00, 0x00 }, + }, + /* FIXME: the following format is not defined properly yet */ + [SNDRV_PCM_FORMAT_SPECIAL] = { + .le = -1, .signd = -1, + }, + [SNDRV_PCM_FORMAT_S24_3LE] = { + .width = 24, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S24_3BE] = { + .width = 24, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U24_3LE] = { + .width = 24, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x80 }, + }, + [SNDRV_PCM_FORMAT_U24_3BE] = { + .width = 24, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x80, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S20_3LE] = { + .width = 20, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S20_3BE] = { + .width = 20, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U20_3LE] = { + .width = 20, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x08 }, + }, + [SNDRV_PCM_FORMAT_U20_3BE] = { + .width = 20, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x08, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_S18_3LE] = { + .width = 18, .phys = 24, .le = 1, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_S18_3BE] = { + .width = 18, .phys = 24, .le = 0, .signd = 1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_U18_3LE] = { + .width = 18, .phys = 24, .le = 1, .signd = 0, + .silence = { 0x00, 0x00, 0x02 }, + }, + [SNDRV_PCM_FORMAT_U18_3BE] = { + .width = 18, .phys = 24, .le = 0, .signd = 0, + .silence = { 0x02, 0x00, 0x00 }, + }, + [SNDRV_PCM_FORMAT_G723_24_1B] = { + .width = 3, .phys = 8, .le = -1, .signd = -1, + .silence = {}, + }, + [SNDRV_PCM_FORMAT_G723_40_1B] = { + .width = 5, .phys = 8, .le = -1, .signd = -1, + .silence = {}, + }, +}; + + +/** + * snd_pcm_format_signed - Check the PCM format is signed linear + * @format: the format to check + * + * Return: 1 if the given PCM format is signed linear, 0 if unsigned + * linear, and a negative error code for non-linear formats. + */ +int snd_pcm_format_signed(snd_pcm_format_t format) +{ + int val; + if (!valid_format(format)) + return -EINVAL; + val = pcm_formats[(INT)format].signd; + if (val < 0) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_signed); + +/** + * snd_pcm_format_unsigned - Check the PCM format is unsigned linear + * @format: the format to check + * + * Return: 1 if the given PCM format is unsigned linear, 0 if signed + * linear, and a negative error code for non-linear formats. + */ +int snd_pcm_format_unsigned(snd_pcm_format_t format) +{ + int val; + + val = snd_pcm_format_signed(format); + if (val < 0) + return val; + return !val; +} +EXPORT_SYMBOL(snd_pcm_format_unsigned); + +/** + * snd_pcm_format_linear - Check the PCM format is linear + * @format: the format to check + * + * Return: 1 if the given PCM format is linear, 0 if not. + */ +int snd_pcm_format_linear(snd_pcm_format_t format) +{ + return snd_pcm_format_signed(format) >= 0; +} +EXPORT_SYMBOL(snd_pcm_format_linear); + +/** + * snd_pcm_format_little_endian - Check the PCM format is little-endian + * @format: the format to check + * + * Return: 1 if the given PCM format is little-endian, 0 if + * big-endian, or a negative error code if endian not specified. + */ +int snd_pcm_format_little_endian(snd_pcm_format_t format) +{ + int val; + if (!valid_format(format)) + return -EINVAL; + val = pcm_formats[(INT)format].le; + if (val < 0) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_little_endian); + +/** + * snd_pcm_format_big_endian - Check the PCM format is big-endian + * @format: the format to check + * + * Return: 1 if the given PCM format is big-endian, 0 if + * little-endian, or a negative error code if endian not specified. + */ +int snd_pcm_format_big_endian(snd_pcm_format_t format) +{ + int val; + + val = snd_pcm_format_little_endian(format); + if (val < 0) + return val; + return !val; +} +EXPORT_SYMBOL(snd_pcm_format_big_endian); + +/** + * snd_pcm_format_width - return the bit-width of the format + * @format: the format to check + * + * Return: The bit-width of the format, or a negative error code + * if unknown format. + */ +int snd_pcm_format_width(snd_pcm_format_t format) +{ + int val; + if (!valid_format(format)) + return -EINVAL; + val = pcm_formats[(INT)format].width; + if (!val) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_width); + +/** + * snd_pcm_format_physical_width - return the physical bit-width of the format + * @format: the format to check + * + * Return: The physical bit-width of the format, or a negative error code + * if unknown format. + */ +int snd_pcm_format_physical_width(snd_pcm_format_t format) +{ + int val; + if (!valid_format(format)) + return -EINVAL; + val = pcm_formats[(INT)format].phys; + if (!val) + return -EINVAL; + return val; +} +EXPORT_SYMBOL(snd_pcm_format_physical_width); + +/** + * snd_pcm_format_size - return the byte size of samples on the given format + * @format: the format to check + * @samples: sampling rate + * + * Return: The byte size of the given samples for the format, or a + * negative error code if unknown format. + */ +ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples) +{ + int phys_width = snd_pcm_format_physical_width(format); + if (phys_width < 0) + return -EINVAL; + return samples * phys_width / 8; +} +EXPORT_SYMBOL(snd_pcm_format_size); + +/** + * snd_pcm_format_silence_64 - return the silent data in 8 bytes array + * @format: the format to check + * + * Return: The format pattern to fill or %NULL if error. + */ +const unsigned char *snd_pcm_format_silence_64(snd_pcm_format_t format) +{ + if (!valid_format(format)) + return NULL; + if (! pcm_formats[(INT)format].phys) + return NULL; + return pcm_formats[(INT)format].silence; +} +EXPORT_SYMBOL(snd_pcm_format_silence_64); + +/** + * snd_pcm_format_set_silence - set the silence data on the buffer + * @format: the PCM format + * @data: the buffer pointer + * @samples: the number of samples to set silence + * + * Sets the silence data on the buffer for the given samples. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples) +{ + int width; + unsigned char *dst; + const unsigned char *pat; + + if (!valid_format(format)) + return -EINVAL; + if (samples == 0) + return 0; + width = pcm_formats[(INT)format].phys; /* physical width */ + pat = pcm_formats[(INT)format].silence; + if (!width || !pat) + return -EINVAL; + /* signed or 1 byte data */ + if (pcm_formats[(INT)format].signd == 1 || width <= 8) { + unsigned int bytes = samples * width / 8; + memset(data, *pat, bytes); + return 0; + } + /* non-zero samples, fill using a loop */ + width /= 8; + dst = data; +#if 0 + while (samples--) { + memcpy(dst, pat, width); + dst += width; + } +#else + /* a bit optimization for constant width */ + switch (width) { + case 2: + while (samples--) { + memcpy(dst, pat, 2); + dst += 2; + } + break; + case 3: + while (samples--) { + memcpy(dst, pat, 3); + dst += 3; + } + break; + case 4: + while (samples--) { + memcpy(dst, pat, 4); + dst += 4; + } + break; + case 8: + while (samples--) { + memcpy(dst, pat, 8); + dst += 8; + } + break; + } +#endif + return 0; +} +EXPORT_SYMBOL(snd_pcm_format_set_silence); + +/** + * snd_pcm_hw_limit_rates - determine rate_min/rate_max fields + * @hw: the pcm hw instance + * + * Determines the rate_min and rate_max fields from the rates bits of + * the given hw. + * + * Return: Zero if successful. + */ +int snd_pcm_hw_limit_rates(struct snd_pcm_hardware *hw) +{ + int i; + for (i = 0; i < (int)snd_pcm_known_rates.count; i++) { + if (hw->rates & (1 << i)) { + hw->rate_min = snd_pcm_known_rates.list[i]; + break; + } + } + for (i = (int)snd_pcm_known_rates.count - 1; i >= 0; i--) { + if (hw->rates & (1 << i)) { + hw->rate_max = snd_pcm_known_rates.list[i]; + break; + } + } + return 0; +} +EXPORT_SYMBOL(snd_pcm_hw_limit_rates); + +/** + * snd_pcm_rate_to_rate_bit - converts sample rate to SNDRV_PCM_RATE_xxx bit + * @rate: the sample rate to convert + * + * Return: The SNDRV_PCM_RATE_xxx flag that corresponds to the given rate, or + * SNDRV_PCM_RATE_KNOT for an unknown rate. + */ +unsigned int snd_pcm_rate_to_rate_bit(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < snd_pcm_known_rates.count; i++) + if (snd_pcm_known_rates.list[i] == rate) + return 1u << i; + return SNDRV_PCM_RATE_KNOT; +} +EXPORT_SYMBOL(snd_pcm_rate_to_rate_bit); + +/** + * snd_pcm_rate_bit_to_rate - converts SNDRV_PCM_RATE_xxx bit to sample rate + * @rate_bit: the rate bit to convert + * + * Return: The sample rate that corresponds to the given SNDRV_PCM_RATE_xxx flag + * or 0 for an unknown rate bit. + */ +unsigned int snd_pcm_rate_bit_to_rate(unsigned int rate_bit) +{ + unsigned int i; + + for (i = 0; i < snd_pcm_known_rates.count; i++) + if ((1u << i) == rate_bit) + return snd_pcm_known_rates.list[i]; + return 0; +} +EXPORT_SYMBOL(snd_pcm_rate_bit_to_rate); + +static unsigned int snd_pcm_rate_mask_sanitize(unsigned int rates) +{ + if (rates & SNDRV_PCM_RATE_CONTINUOUS) + return SNDRV_PCM_RATE_CONTINUOUS; + else if (rates & SNDRV_PCM_RATE_KNOT) + return SNDRV_PCM_RATE_KNOT; + return rates; +} + +/** + * snd_pcm_rate_mask_intersect - computes the intersection between two rate masks + * @rates_a: The first rate mask + * @rates_b: The second rate mask + * + * This function computes the rates that are supported by both rate masks passed + * to the function. It will take care of the special handling of + * SNDRV_PCM_RATE_CONTINUOUS and SNDRV_PCM_RATE_KNOT. + * + * Return: A rate mask containing the rates that are supported by both rates_a + * and rates_b. + */ +unsigned int snd_pcm_rate_mask_intersect(unsigned int rates_a, + unsigned int rates_b) +{ + rates_a = snd_pcm_rate_mask_sanitize(rates_a); + rates_b = snd_pcm_rate_mask_sanitize(rates_b); + + if (rates_a & SNDRV_PCM_RATE_CONTINUOUS) + return rates_b; + else if (rates_b & SNDRV_PCM_RATE_CONTINUOUS) + return rates_a; + else if (rates_a & SNDRV_PCM_RATE_KNOT) + return rates_b; + else if (rates_b & SNDRV_PCM_RATE_KNOT) + return rates_a; + return rates_a & rates_b; +} +EXPORT_SYMBOL_GPL(snd_pcm_rate_mask_intersect); + +/** + * snd_pcm_rate_range_to_bits - converts rate range to SNDRV_PCM_RATE_xxx bit + * @rate_min: the minimum sample rate + * @rate_max: the maximum sample rate + * + * This function has an implicit assumption: the rates in the given range have + * only the pre-defined rates like 44100 or 16000. + * + * Return: The SNDRV_PCM_RATE_xxx flag that corresponds to the given rate range, + * or SNDRV_PCM_RATE_KNOT for an unknown range. + */ +unsigned int snd_pcm_rate_range_to_bits(unsigned int rate_min, + unsigned int rate_max) +{ + unsigned int rates = 0; + int i; + + for (i = 0; i < snd_pcm_known_rates.count; i++) { + if (snd_pcm_known_rates.list[i] >= rate_min + && snd_pcm_known_rates.list[i] <= rate_max) + rates |= 1 << i; + } + + if (!rates) + rates = SNDRV_PCM_RATE_KNOT; + + return rates; +} +EXPORT_SYMBOL_GPL(snd_pcm_rate_range_to_bits); diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c new file mode 100644 index 0000000000..bd9ddf412b --- /dev/null +++ b/sound/core/pcm_native.c @@ -0,0 +1,4151 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/compat.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/file.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> +#include <linux/time.h> +#include <linux/pm_qos.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/timer.h> +#include <sound/minors.h> +#include <linux/uio.h> +#include <linux/delay.h> + +#include "pcm_local.h" + +#ifdef CONFIG_SND_DEBUG +#define CREATE_TRACE_POINTS +#include "pcm_param_trace.h" +#else +#define trace_hw_mask_param_enabled() 0 +#define trace_hw_interval_param_enabled() 0 +#define trace_hw_mask_param(substream, type, index, prev, curr) +#define trace_hw_interval_param(substream, type, index, prev, curr) +#endif + +/* + * Compatibility + */ + +struct snd_pcm_hw_params_old { + unsigned int flags; + unsigned int masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - + SNDRV_PCM_HW_PARAM_ACCESS + 1]; + struct snd_interval intervals[SNDRV_PCM_HW_PARAM_TICK_TIME - + SNDRV_PCM_HW_PARAM_SAMPLE_BITS + 1]; + unsigned int rmask; + unsigned int cmask; + unsigned int info; + unsigned int msbits; + unsigned int rate_num; + unsigned int rate_den; + snd_pcm_uframes_t fifo_size; + unsigned char reserved[64]; +}; + +#ifdef CONFIG_SND_SUPPORT_OLD_API +#define SNDRV_PCM_IOCTL_HW_REFINE_OLD _IOWR('A', 0x10, struct snd_pcm_hw_params_old) +#define SNDRV_PCM_IOCTL_HW_PARAMS_OLD _IOWR('A', 0x11, struct snd_pcm_hw_params_old) + +static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams); +static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams); +#endif +static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream); + +/* + * + */ + +static DECLARE_RWSEM(snd_pcm_link_rwsem); + +void snd_pcm_group_init(struct snd_pcm_group *group) +{ + spin_lock_init(&group->lock); + mutex_init(&group->mutex); + INIT_LIST_HEAD(&group->substreams); + refcount_set(&group->refs, 1); +} + +/* define group lock helpers */ +#define DEFINE_PCM_GROUP_LOCK(action, mutex_action) \ +static void snd_pcm_group_ ## action(struct snd_pcm_group *group, bool nonatomic) \ +{ \ + if (nonatomic) \ + mutex_ ## mutex_action(&group->mutex); \ + else \ + spin_ ## action(&group->lock); \ +} + +DEFINE_PCM_GROUP_LOCK(lock, lock); +DEFINE_PCM_GROUP_LOCK(unlock, unlock); +DEFINE_PCM_GROUP_LOCK(lock_irq, lock); +DEFINE_PCM_GROUP_LOCK(unlock_irq, unlock); + +/** + * snd_pcm_stream_lock - Lock the PCM stream + * @substream: PCM substream + * + * This locks the PCM stream's spinlock or mutex depending on the nonatomic + * flag of the given substream. This also takes the global link rw lock + * (or rw sem), too, for avoiding the race with linked streams. + */ +void snd_pcm_stream_lock(struct snd_pcm_substream *substream) +{ + snd_pcm_group_lock(&substream->self_group, substream->pcm->nonatomic); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_lock); + +/** + * snd_pcm_stream_unlock - Unlock the PCM stream + * @substream: PCM substream + * + * This unlocks the PCM stream that has been locked via snd_pcm_stream_lock(). + */ +void snd_pcm_stream_unlock(struct snd_pcm_substream *substream) +{ + snd_pcm_group_unlock(&substream->self_group, substream->pcm->nonatomic); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock); + +/** + * snd_pcm_stream_lock_irq - Lock the PCM stream + * @substream: PCM substream + * + * This locks the PCM stream like snd_pcm_stream_lock() and disables the local + * IRQ (only when nonatomic is false). In nonatomic case, this is identical + * as snd_pcm_stream_lock(). + */ +void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream) +{ + snd_pcm_group_lock_irq(&substream->self_group, + substream->pcm->nonatomic); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq); + +static void snd_pcm_stream_lock_nested(struct snd_pcm_substream *substream) +{ + struct snd_pcm_group *group = &substream->self_group; + + if (substream->pcm->nonatomic) + mutex_lock_nested(&group->mutex, SINGLE_DEPTH_NESTING); + else + spin_lock_nested(&group->lock, SINGLE_DEPTH_NESTING); +} + +/** + * snd_pcm_stream_unlock_irq - Unlock the PCM stream + * @substream: PCM substream + * + * This is a counter-part of snd_pcm_stream_lock_irq(). + */ +void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream) +{ + snd_pcm_group_unlock_irq(&substream->self_group, + substream->pcm->nonatomic); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irq); + +unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream) +{ + unsigned long flags = 0; + if (substream->pcm->nonatomic) + mutex_lock(&substream->self_group.mutex); + else + spin_lock_irqsave(&substream->self_group.lock, flags); + return flags; +} +EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave); + +unsigned long _snd_pcm_stream_lock_irqsave_nested(struct snd_pcm_substream *substream) +{ + unsigned long flags = 0; + if (substream->pcm->nonatomic) + mutex_lock_nested(&substream->self_group.mutex, + SINGLE_DEPTH_NESTING); + else + spin_lock_irqsave_nested(&substream->self_group.lock, flags, + SINGLE_DEPTH_NESTING); + return flags; +} +EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave_nested); + +/** + * snd_pcm_stream_unlock_irqrestore - Unlock the PCM stream + * @substream: PCM substream + * @flags: irq flags + * + * This is a counter-part of snd_pcm_stream_lock_irqsave(). + */ +void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream, + unsigned long flags) +{ + if (substream->pcm->nonatomic) + mutex_unlock(&substream->self_group.mutex); + else + spin_unlock_irqrestore(&substream->self_group.lock, flags); +} +EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore); + +/* Run PCM ioctl ops */ +static int snd_pcm_ops_ioctl(struct snd_pcm_substream *substream, + unsigned cmd, void *arg) +{ + if (substream->ops->ioctl) + return substream->ops->ioctl(substream, cmd, arg); + else + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +int snd_pcm_info(struct snd_pcm_substream *substream, struct snd_pcm_info *info) +{ + struct snd_pcm *pcm = substream->pcm; + struct snd_pcm_str *pstr = substream->pstr; + + memset(info, 0, sizeof(*info)); + info->card = pcm->card->number; + info->device = pcm->device; + info->stream = substream->stream; + info->subdevice = substream->number; + strscpy(info->id, pcm->id, sizeof(info->id)); + strscpy(info->name, pcm->name, sizeof(info->name)); + info->dev_class = pcm->dev_class; + info->dev_subclass = pcm->dev_subclass; + info->subdevices_count = pstr->substream_count; + info->subdevices_avail = pstr->substream_count - pstr->substream_opened; + strscpy(info->subname, substream->name, sizeof(info->subname)); + + return 0; +} + +int snd_pcm_info_user(struct snd_pcm_substream *substream, + struct snd_pcm_info __user * _info) +{ + struct snd_pcm_info *info; + int err; + + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + err = snd_pcm_info(substream, info); + if (err >= 0) { + if (copy_to_user(_info, info, sizeof(*info))) + err = -EFAULT; + } + kfree(info); + return err; +} + +/* macro for simplified cast */ +#define PARAM_MASK_BIT(b) (1U << (__force int)(b)) + +static bool hw_support_mmap(struct snd_pcm_substream *substream) +{ + struct snd_dma_buffer *dmabuf; + + if (!(substream->runtime->hw.info & SNDRV_PCM_INFO_MMAP)) + return false; + + if (substream->ops->mmap || substream->ops->page) + return true; + + dmabuf = snd_pcm_get_dma_buf(substream); + if (!dmabuf) + dmabuf = &substream->dma_buffer; + switch (dmabuf->dev.type) { + case SNDRV_DMA_TYPE_UNKNOWN: + /* we can't know the device, so just assume that the driver does + * everything right + */ + return true; + case SNDRV_DMA_TYPE_CONTINUOUS: + case SNDRV_DMA_TYPE_VMALLOC: + return true; + default: + return dma_can_mmap(dmabuf->dev.dev); + } +} + +static int constrain_mask_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_hw_constraints *constrs = + &substream->runtime->hw_constraints; + struct snd_mask *m; + unsigned int k; + struct snd_mask old_mask __maybe_unused; + int changed; + + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) { + m = hw_param_mask(params, k); + if (snd_mask_empty(m)) + return -EINVAL; + + /* This parameter is not requested to change by a caller. */ + if (!(params->rmask & PARAM_MASK_BIT(k))) + continue; + + if (trace_hw_mask_param_enabled()) + old_mask = *m; + + changed = snd_mask_refine(m, constrs_mask(constrs, k)); + if (changed < 0) + return changed; + if (changed == 0) + continue; + + /* Set corresponding flag so that the caller gets it. */ + trace_hw_mask_param(substream, k, 0, &old_mask, m); + params->cmask |= PARAM_MASK_BIT(k); + } + + return 0; +} + +static int constrain_interval_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_hw_constraints *constrs = + &substream->runtime->hw_constraints; + struct snd_interval *i; + unsigned int k; + struct snd_interval old_interval __maybe_unused; + int changed; + + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) { + i = hw_param_interval(params, k); + if (snd_interval_empty(i)) + return -EINVAL; + + /* This parameter is not requested to change by a caller. */ + if (!(params->rmask & PARAM_MASK_BIT(k))) + continue; + + if (trace_hw_interval_param_enabled()) + old_interval = *i; + + changed = snd_interval_refine(i, constrs_interval(constrs, k)); + if (changed < 0) + return changed; + if (changed == 0) + continue; + + /* Set corresponding flag so that the caller gets it. */ + trace_hw_interval_param(substream, k, 0, &old_interval, i); + params->cmask |= PARAM_MASK_BIT(k); + } + + return 0; +} + +static int constrain_params_by_rules(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_hw_constraints *constrs = + &substream->runtime->hw_constraints; + unsigned int k; + unsigned int *rstamps; + unsigned int vstamps[SNDRV_PCM_HW_PARAM_LAST_INTERVAL + 1]; + unsigned int stamp; + struct snd_pcm_hw_rule *r; + unsigned int d; + struct snd_mask old_mask __maybe_unused; + struct snd_interval old_interval __maybe_unused; + bool again; + int changed, err = 0; + + /* + * Each application of rule has own sequence number. + * + * Each member of 'rstamps' array represents the sequence number of + * recent application of corresponding rule. + */ + rstamps = kcalloc(constrs->rules_num, sizeof(unsigned int), GFP_KERNEL); + if (!rstamps) + return -ENOMEM; + + /* + * Each member of 'vstamps' array represents the sequence number of + * recent application of rule in which corresponding parameters were + * changed. + * + * In initial state, elements corresponding to parameters requested by + * a caller is 1. For unrequested parameters, corresponding members + * have 0 so that the parameters are never changed anymore. + */ + for (k = 0; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) + vstamps[k] = (params->rmask & PARAM_MASK_BIT(k)) ? 1 : 0; + + /* Due to the above design, actual sequence number starts at 2. */ + stamp = 2; +retry: + /* Apply all rules in order. */ + again = false; + for (k = 0; k < constrs->rules_num; k++) { + r = &constrs->rules[k]; + + /* + * Check condition bits of this rule. When the rule has + * some condition bits, parameter without the bits is + * never processed. SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP + * is an example of the condition bits. + */ + if (r->cond && !(r->cond & params->flags)) + continue; + + /* + * The 'deps' array includes maximum four dependencies + * to SNDRV_PCM_HW_PARAM_XXXs for this rule. The fifth + * member of this array is a sentinel and should be + * negative value. + * + * This rule should be processed in this time when dependent + * parameters were changed at former applications of the other + * rules. + */ + for (d = 0; r->deps[d] >= 0; d++) { + if (vstamps[r->deps[d]] > rstamps[k]) + break; + } + if (r->deps[d] < 0) + continue; + + if (trace_hw_mask_param_enabled()) { + if (hw_is_mask(r->var)) + old_mask = *hw_param_mask(params, r->var); + } + if (trace_hw_interval_param_enabled()) { + if (hw_is_interval(r->var)) + old_interval = *hw_param_interval(params, r->var); + } + + changed = r->func(params, r); + if (changed < 0) { + err = changed; + goto out; + } + + /* + * When the parameter is changed, notify it to the caller + * by corresponding returned bit, then preparing for next + * iteration. + */ + if (changed && r->var >= 0) { + if (hw_is_mask(r->var)) { + trace_hw_mask_param(substream, r->var, + k + 1, &old_mask, + hw_param_mask(params, r->var)); + } + if (hw_is_interval(r->var)) { + trace_hw_interval_param(substream, r->var, + k + 1, &old_interval, + hw_param_interval(params, r->var)); + } + + params->cmask |= PARAM_MASK_BIT(r->var); + vstamps[r->var] = stamp; + again = true; + } + + rstamps[k] = stamp++; + } + + /* Iterate to evaluate all rules till no parameters are changed. */ + if (again) + goto retry; + + out: + kfree(rstamps); + return err; +} + +static int fixup_unreferenced_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + const struct snd_interval *i; + const struct snd_mask *m; + int err; + + if (!params->msbits) { + i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); + if (snd_interval_single(i)) + params->msbits = snd_interval_value(i); + } + + if (!params->rate_den) { + i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + if (snd_interval_single(i)) { + params->rate_num = snd_interval_value(i); + params->rate_den = 1; + } + } + + if (!params->fifo_size) { + m = hw_param_mask_c(params, SNDRV_PCM_HW_PARAM_FORMAT); + i = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (snd_mask_single(m) && snd_interval_single(i)) { + err = snd_pcm_ops_ioctl(substream, + SNDRV_PCM_IOCTL1_FIFO_SIZE, + params); + if (err < 0) + return err; + } + } + + if (!params->info) { + params->info = substream->runtime->hw.info; + params->info &= ~(SNDRV_PCM_INFO_FIFO_IN_FRAMES | + SNDRV_PCM_INFO_DRAIN_TRIGGER); + if (!hw_support_mmap(substream)) + params->info &= ~(SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID); + } + + return 0; +} + +int snd_pcm_hw_refine(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + int err; + + params->info = 0; + params->fifo_size = 0; + if (params->rmask & PARAM_MASK_BIT(SNDRV_PCM_HW_PARAM_SAMPLE_BITS)) + params->msbits = 0; + if (params->rmask & PARAM_MASK_BIT(SNDRV_PCM_HW_PARAM_RATE)) { + params->rate_num = 0; + params->rate_den = 0; + } + + err = constrain_mask_params(substream, params); + if (err < 0) + return err; + + err = constrain_interval_params(substream, params); + if (err < 0) + return err; + + err = constrain_params_by_rules(substream, params); + if (err < 0) + return err; + + params->rmask = 0; + + return 0; +} +EXPORT_SYMBOL(snd_pcm_hw_refine); + +static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + struct snd_pcm_hw_params *params; + int err; + + params = memdup_user(_params, sizeof(*params)); + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto end; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + goto end; + + if (copy_to_user(_params, params, sizeof(*params))) + err = -EFAULT; +end: + kfree(params); + return err; +} + +static int period_to_usecs(struct snd_pcm_runtime *runtime) +{ + int usecs; + + if (! runtime->rate) + return -1; /* invalid */ + + /* take 75% of period time as the deadline */ + usecs = (750000 / runtime->rate) * runtime->period_size; + usecs += ((750000 % runtime->rate) * runtime->period_size) / + runtime->rate; + + return usecs; +} + +static void snd_pcm_set_state(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + snd_pcm_stream_lock_irq(substream); + if (substream->runtime->state != SNDRV_PCM_STATE_DISCONNECTED) + __snd_pcm_set_state(substream->runtime, state); + snd_pcm_stream_unlock_irq(substream); +} + +static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream, + int event) +{ +#ifdef CONFIG_SND_PCM_TIMER + if (substream->timer) + snd_timer_notify(substream->timer, event, + &substream->runtime->trigger_tstamp); +#endif +} + +void snd_pcm_sync_stop(struct snd_pcm_substream *substream, bool sync_irq) +{ + if (substream->runtime && substream->runtime->stop_operating) { + substream->runtime->stop_operating = false; + if (substream->ops && substream->ops->sync_stop) + substream->ops->sync_stop(substream); + else if (sync_irq && substream->pcm->card->sync_irq > 0) + synchronize_irq(substream->pcm->card->sync_irq); + } +} + +/** + * snd_pcm_hw_params_choose - choose a configuration defined by @params + * @pcm: PCM instance + * @params: the hw_params instance + * + * Choose one configuration from configuration space defined by @params. + * The configuration chosen is that obtained fixing in this order: + * first access, first format, first subformat, min channels, + * min rate, min period time, max buffer size, min tick time + * + * Return: Zero if successful, or a negative error code on failure. + */ +static int snd_pcm_hw_params_choose(struct snd_pcm_substream *pcm, + struct snd_pcm_hw_params *params) +{ + static const int vars[] = { + SNDRV_PCM_HW_PARAM_ACCESS, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_SUBFORMAT, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + SNDRV_PCM_HW_PARAM_TICK_TIME, + -1 + }; + const int *v; + struct snd_mask old_mask __maybe_unused; + struct snd_interval old_interval __maybe_unused; + int changed; + + for (v = vars; *v != -1; v++) { + /* Keep old parameter to trace. */ + if (trace_hw_mask_param_enabled()) { + if (hw_is_mask(*v)) + old_mask = *hw_param_mask(params, *v); + } + if (trace_hw_interval_param_enabled()) { + if (hw_is_interval(*v)) + old_interval = *hw_param_interval(params, *v); + } + if (*v != SNDRV_PCM_HW_PARAM_BUFFER_SIZE) + changed = snd_pcm_hw_param_first(pcm, params, *v, NULL); + else + changed = snd_pcm_hw_param_last(pcm, params, *v, NULL); + if (changed < 0) + return changed; + if (changed == 0) + continue; + + /* Trace the changed parameter. */ + if (hw_is_mask(*v)) { + trace_hw_mask_param(pcm, *v, 0, &old_mask, + hw_param_mask(params, *v)); + } + if (hw_is_interval(*v)) { + trace_hw_interval_param(pcm, *v, 0, &old_interval, + hw_param_interval(params, *v)); + } + } + + return 0; +} + +/* acquire buffer_mutex; if it's in r/w operation, return -EBUSY, otherwise + * block the further r/w operations + */ +static int snd_pcm_buffer_access_lock(struct snd_pcm_runtime *runtime) +{ + if (!atomic_dec_unless_positive(&runtime->buffer_accessing)) + return -EBUSY; + mutex_lock(&runtime->buffer_mutex); + return 0; /* keep buffer_mutex, unlocked by below */ +} + +/* release buffer_mutex and clear r/w access flag */ +static void snd_pcm_buffer_access_unlock(struct snd_pcm_runtime *runtime) +{ + mutex_unlock(&runtime->buffer_mutex); + atomic_inc(&runtime->buffer_accessing); +} + +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#define is_oss_stream(substream) ((substream)->oss.oss) +#else +#define is_oss_stream(substream) false +#endif + +static int snd_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err, usecs; + unsigned int bits; + snd_pcm_uframes_t frames; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + err = snd_pcm_buffer_access_lock(runtime); + if (err < 0) + return err; + snd_pcm_stream_lock_irq(substream); + switch (runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + if (!is_oss_stream(substream) && + atomic_read(&substream->mmap_count)) + err = -EBADFD; + break; + default: + err = -EBADFD; + break; + } + snd_pcm_stream_unlock_irq(substream); + if (err) + goto unlock; + + snd_pcm_sync_stop(substream, true); + + params->rmask = ~0U; + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto _error; + + err = snd_pcm_hw_params_choose(substream, params); + if (err < 0) + goto _error; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + goto _error; + + if (substream->managed_buffer_alloc) { + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(params)); + if (err < 0) + goto _error; + runtime->buffer_changed = err > 0; + } + + if (substream->ops->hw_params != NULL) { + err = substream->ops->hw_params(substream, params); + if (err < 0) + goto _error; + } + + runtime->access = params_access(params); + runtime->format = params_format(params); + runtime->subformat = params_subformat(params); + runtime->channels = params_channels(params); + runtime->rate = params_rate(params); + runtime->period_size = params_period_size(params); + runtime->periods = params_periods(params); + runtime->buffer_size = params_buffer_size(params); + runtime->info = params->info; + runtime->rate_num = params->rate_num; + runtime->rate_den = params->rate_den; + runtime->no_period_wakeup = + (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) && + (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP); + + bits = snd_pcm_format_physical_width(runtime->format); + runtime->sample_bits = bits; + bits *= runtime->channels; + runtime->frame_bits = bits; + frames = 1; + while (bits % 8 != 0) { + bits *= 2; + frames *= 2; + } + runtime->byte_align = bits / 8; + runtime->min_align = frames; + + /* Default sw params */ + runtime->tstamp_mode = SNDRV_PCM_TSTAMP_NONE; + runtime->period_step = 1; + runtime->control->avail_min = runtime->period_size; + runtime->start_threshold = 1; + runtime->stop_threshold = runtime->buffer_size; + runtime->silence_threshold = 0; + runtime->silence_size = 0; + runtime->boundary = runtime->buffer_size; + while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size) + runtime->boundary *= 2; + + /* clear the buffer for avoiding possible kernel info leaks */ + if (runtime->dma_area && !substream->ops->copy) { + size_t size = runtime->dma_bytes; + + if (runtime->info & SNDRV_PCM_INFO_MMAP) + size = PAGE_ALIGN(size); + memset(runtime->dma_area, 0, size); + } + + snd_pcm_timer_resolution_change(substream); + snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP); + + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + usecs = period_to_usecs(runtime); + if (usecs >= 0) + cpu_latency_qos_add_request(&substream->latency_pm_qos_req, + usecs); + err = 0; + _error: + if (err) { + /* hardware might be unusable from this time, + * so we force application to retry to set + * the correct hardware parameter settings + */ + snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); + if (substream->ops->hw_free != NULL) + substream->ops->hw_free(substream); + if (substream->managed_buffer_alloc) + snd_pcm_lib_free_pages(substream); + } + unlock: + snd_pcm_buffer_access_unlock(runtime); + return err; +} + +static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + struct snd_pcm_hw_params *params; + int err; + + params = memdup_user(_params, sizeof(*params)); + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_params(substream, params); + if (err < 0) + goto end; + + if (copy_to_user(_params, params, sizeof(*params))) + err = -EFAULT; +end: + kfree(params); + return err; +} + +static int do_hw_free(struct snd_pcm_substream *substream) +{ + int result = 0; + + snd_pcm_sync_stop(substream, true); + if (substream->ops->hw_free) + result = substream->ops->hw_free(substream); + if (substream->managed_buffer_alloc) + snd_pcm_lib_free_pages(substream); + return result; +} + +static int snd_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int result = 0; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + result = snd_pcm_buffer_access_lock(runtime); + if (result < 0) + return result; + snd_pcm_stream_lock_irq(substream); + switch (runtime->state) { + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + if (atomic_read(&substream->mmap_count)) + result = -EBADFD; + break; + default: + result = -EBADFD; + break; + } + snd_pcm_stream_unlock_irq(substream); + if (result) + goto unlock; + result = do_hw_free(substream); + snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + unlock: + snd_pcm_buffer_access_unlock(runtime); + return result; +} + +static int snd_pcm_sw_params(struct snd_pcm_substream *substream, + struct snd_pcm_sw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + snd_pcm_stream_lock_irq(substream); + if (runtime->state == SNDRV_PCM_STATE_OPEN) { + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + + if (params->tstamp_mode < 0 || + params->tstamp_mode > SNDRV_PCM_TSTAMP_LAST) + return -EINVAL; + if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12) && + params->tstamp_type > SNDRV_PCM_TSTAMP_TYPE_LAST) + return -EINVAL; + if (params->avail_min == 0) + return -EINVAL; + if (params->silence_size >= runtime->boundary) { + if (params->silence_threshold != 0) + return -EINVAL; + } else { + if (params->silence_size > params->silence_threshold) + return -EINVAL; + if (params->silence_threshold > runtime->buffer_size) + return -EINVAL; + } + err = 0; + snd_pcm_stream_lock_irq(substream); + runtime->tstamp_mode = params->tstamp_mode; + if (params->proto >= SNDRV_PROTOCOL_VERSION(2, 0, 12)) + runtime->tstamp_type = params->tstamp_type; + runtime->period_step = params->period_step; + runtime->control->avail_min = params->avail_min; + runtime->start_threshold = params->start_threshold; + runtime->stop_threshold = params->stop_threshold; + runtime->silence_threshold = params->silence_threshold; + runtime->silence_size = params->silence_size; + params->boundary = runtime->boundary; + if (snd_pcm_running(substream)) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + err = snd_pcm_update_state(substream, runtime); + } + snd_pcm_stream_unlock_irq(substream); + return err; +} + +static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream, + struct snd_pcm_sw_params __user * _params) +{ + struct snd_pcm_sw_params params; + int err; + if (copy_from_user(¶ms, _params, sizeof(params))) + return -EFAULT; + err = snd_pcm_sw_params(substream, ¶ms); + if (copy_to_user(_params, ¶ms, sizeof(params))) + return -EFAULT; + return err; +} + +static inline snd_pcm_uframes_t +snd_pcm_calc_delay(struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t delay; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + delay = snd_pcm_playback_hw_avail(substream->runtime); + else + delay = snd_pcm_capture_avail(substream->runtime); + return delay + substream->runtime->delay; +} + +int snd_pcm_status64(struct snd_pcm_substream *substream, + struct snd_pcm_status64 *status) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_stream_lock_irq(substream); + + snd_pcm_unpack_audio_tstamp_config(status->audio_tstamp_data, + &runtime->audio_tstamp_config); + + /* backwards compatible behavior */ + if (runtime->audio_tstamp_config.type_requested == + SNDRV_PCM_AUDIO_TSTAMP_TYPE_COMPAT) { + if (runtime->hw.info & SNDRV_PCM_INFO_HAS_WALL_CLOCK) + runtime->audio_tstamp_config.type_requested = + SNDRV_PCM_AUDIO_TSTAMP_TYPE_LINK; + else + runtime->audio_tstamp_config.type_requested = + SNDRV_PCM_AUDIO_TSTAMP_TYPE_DEFAULT; + runtime->audio_tstamp_report.valid = 0; + } else + runtime->audio_tstamp_report.valid = 1; + + status->state = runtime->state; + status->suspended_state = runtime->suspended_state; + if (status->state == SNDRV_PCM_STATE_OPEN) + goto _end; + status->trigger_tstamp_sec = runtime->trigger_tstamp.tv_sec; + status->trigger_tstamp_nsec = runtime->trigger_tstamp.tv_nsec; + if (snd_pcm_running(substream)) { + snd_pcm_update_hw_ptr(substream); + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { + status->tstamp_sec = runtime->status->tstamp.tv_sec; + status->tstamp_nsec = + runtime->status->tstamp.tv_nsec; + status->driver_tstamp_sec = + runtime->driver_tstamp.tv_sec; + status->driver_tstamp_nsec = + runtime->driver_tstamp.tv_nsec; + status->audio_tstamp_sec = + runtime->status->audio_tstamp.tv_sec; + status->audio_tstamp_nsec = + runtime->status->audio_tstamp.tv_nsec; + if (runtime->audio_tstamp_report.valid == 1) + /* backwards compatibility, no report provided in COMPAT mode */ + snd_pcm_pack_audio_tstamp_report(&status->audio_tstamp_data, + &status->audio_tstamp_accuracy, + &runtime->audio_tstamp_report); + + goto _tstamp_end; + } + } else { + /* get tstamp only in fallback mode and only if enabled */ + if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE) { + struct timespec64 tstamp; + + snd_pcm_gettime(runtime, &tstamp); + status->tstamp_sec = tstamp.tv_sec; + status->tstamp_nsec = tstamp.tv_nsec; + } + } + _tstamp_end: + status->appl_ptr = runtime->control->appl_ptr; + status->hw_ptr = runtime->status->hw_ptr; + status->avail = snd_pcm_avail(substream); + status->delay = snd_pcm_running(substream) ? + snd_pcm_calc_delay(substream) : 0; + status->avail_max = runtime->avail_max; + status->overrange = runtime->overrange; + runtime->avail_max = 0; + runtime->overrange = 0; + _end: + snd_pcm_stream_unlock_irq(substream); + return 0; +} + +static int snd_pcm_status_user64(struct snd_pcm_substream *substream, + struct snd_pcm_status64 __user * _status, + bool ext) +{ + struct snd_pcm_status64 status; + int res; + + memset(&status, 0, sizeof(status)); + /* + * with extension, parameters are read/write, + * get audio_tstamp_data from user, + * ignore rest of status structure + */ + if (ext && get_user(status.audio_tstamp_data, + (u32 __user *)(&_status->audio_tstamp_data))) + return -EFAULT; + res = snd_pcm_status64(substream, &status); + if (res < 0) + return res; + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +static int snd_pcm_status_user32(struct snd_pcm_substream *substream, + struct snd_pcm_status32 __user * _status, + bool ext) +{ + struct snd_pcm_status64 status64; + struct snd_pcm_status32 status32; + int res; + + memset(&status64, 0, sizeof(status64)); + memset(&status32, 0, sizeof(status32)); + /* + * with extension, parameters are read/write, + * get audio_tstamp_data from user, + * ignore rest of status structure + */ + if (ext && get_user(status64.audio_tstamp_data, + (u32 __user *)(&_status->audio_tstamp_data))) + return -EFAULT; + res = snd_pcm_status64(substream, &status64); + if (res < 0) + return res; + + status32 = (struct snd_pcm_status32) { + .state = status64.state, + .trigger_tstamp_sec = status64.trigger_tstamp_sec, + .trigger_tstamp_nsec = status64.trigger_tstamp_nsec, + .tstamp_sec = status64.tstamp_sec, + .tstamp_nsec = status64.tstamp_nsec, + .appl_ptr = status64.appl_ptr, + .hw_ptr = status64.hw_ptr, + .delay = status64.delay, + .avail = status64.avail, + .avail_max = status64.avail_max, + .overrange = status64.overrange, + .suspended_state = status64.suspended_state, + .audio_tstamp_data = status64.audio_tstamp_data, + .audio_tstamp_sec = status64.audio_tstamp_sec, + .audio_tstamp_nsec = status64.audio_tstamp_nsec, + .driver_tstamp_sec = status64.audio_tstamp_sec, + .driver_tstamp_nsec = status64.audio_tstamp_nsec, + .audio_tstamp_accuracy = status64.audio_tstamp_accuracy, + }; + + if (copy_to_user(_status, &status32, sizeof(status32))) + return -EFAULT; + + return 0; +} + +static int snd_pcm_channel_info(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info * info) +{ + struct snd_pcm_runtime *runtime; + unsigned int channel; + + channel = info->channel; + runtime = substream->runtime; + snd_pcm_stream_lock_irq(substream); + if (runtime->state == SNDRV_PCM_STATE_OPEN) { + snd_pcm_stream_unlock_irq(substream); + return -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + if (channel >= runtime->channels) + return -EINVAL; + memset(info, 0, sizeof(*info)); + info->channel = channel; + return snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_CHANNEL_INFO, info); +} + +static int snd_pcm_channel_info_user(struct snd_pcm_substream *substream, + struct snd_pcm_channel_info __user * _info) +{ + struct snd_pcm_channel_info info; + int res; + + if (copy_from_user(&info, _info, sizeof(info))) + return -EFAULT; + res = snd_pcm_channel_info(substream, &info); + if (res < 0) + return res; + if (copy_to_user(_info, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static void snd_pcm_trigger_tstamp(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->trigger_master == NULL) + return; + if (runtime->trigger_master == substream) { + if (!runtime->trigger_tstamp_latched) + snd_pcm_gettime(runtime, &runtime->trigger_tstamp); + } else { + snd_pcm_trigger_tstamp(runtime->trigger_master); + runtime->trigger_tstamp = runtime->trigger_master->runtime->trigger_tstamp; + } + runtime->trigger_master = NULL; +} + +#define ACTION_ARG_IGNORE (__force snd_pcm_state_t)0 + +struct action_ops { + int (*pre_action)(struct snd_pcm_substream *substream, + snd_pcm_state_t state); + int (*do_action)(struct snd_pcm_substream *substream, + snd_pcm_state_t state); + void (*undo_action)(struct snd_pcm_substream *substream, + snd_pcm_state_t state); + void (*post_action)(struct snd_pcm_substream *substream, + snd_pcm_state_t state); +}; + +/* + * this functions is core for handling of linked stream + * Note: the stream state might be changed also on failure + * Note2: call with calling stream lock + link lock + */ +static int snd_pcm_action_group(const struct action_ops *ops, + struct snd_pcm_substream *substream, + snd_pcm_state_t state, + bool stream_lock) +{ + struct snd_pcm_substream *s = NULL; + struct snd_pcm_substream *s1; + int res = 0, depth = 1; + + snd_pcm_group_for_each_entry(s, substream) { + if (s != substream) { + if (!stream_lock) + mutex_lock_nested(&s->runtime->buffer_mutex, depth); + else if (s->pcm->nonatomic) + mutex_lock_nested(&s->self_group.mutex, depth); + else + spin_lock_nested(&s->self_group.lock, depth); + depth++; + } + res = ops->pre_action(s, state); + if (res < 0) + goto _unlock; + } + snd_pcm_group_for_each_entry(s, substream) { + res = ops->do_action(s, state); + if (res < 0) { + if (ops->undo_action) { + snd_pcm_group_for_each_entry(s1, substream) { + if (s1 == s) /* failed stream */ + break; + ops->undo_action(s1, state); + } + } + s = NULL; /* unlock all */ + goto _unlock; + } + } + snd_pcm_group_for_each_entry(s, substream) { + ops->post_action(s, state); + } + _unlock: + /* unlock streams */ + snd_pcm_group_for_each_entry(s1, substream) { + if (s1 != substream) { + if (!stream_lock) + mutex_unlock(&s1->runtime->buffer_mutex); + else if (s1->pcm->nonatomic) + mutex_unlock(&s1->self_group.mutex); + else + spin_unlock(&s1->self_group.lock); + } + if (s1 == s) /* end */ + break; + } + return res; +} + +/* + * Note: call with stream lock + */ +static int snd_pcm_action_single(const struct action_ops *ops, + struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + int res; + + res = ops->pre_action(substream, state); + if (res < 0) + return res; + res = ops->do_action(substream, state); + if (res == 0) + ops->post_action(substream, state); + else if (ops->undo_action) + ops->undo_action(substream, state); + return res; +} + +static void snd_pcm_group_assign(struct snd_pcm_substream *substream, + struct snd_pcm_group *new_group) +{ + substream->group = new_group; + list_move(&substream->link_list, &new_group->substreams); +} + +/* + * Unref and unlock the group, but keep the stream lock; + * when the group becomes empty and no longer referred, destroy itself + */ +static void snd_pcm_group_unref(struct snd_pcm_group *group, + struct snd_pcm_substream *substream) +{ + bool do_free; + + if (!group) + return; + do_free = refcount_dec_and_test(&group->refs); + snd_pcm_group_unlock(group, substream->pcm->nonatomic); + if (do_free) + kfree(group); +} + +/* + * Lock the group inside a stream lock and reference it; + * return the locked group object, or NULL if not linked + */ +static struct snd_pcm_group * +snd_pcm_stream_group_ref(struct snd_pcm_substream *substream) +{ + bool nonatomic = substream->pcm->nonatomic; + struct snd_pcm_group *group; + bool trylock; + + for (;;) { + if (!snd_pcm_stream_linked(substream)) + return NULL; + group = substream->group; + /* block freeing the group object */ + refcount_inc(&group->refs); + + trylock = nonatomic ? mutex_trylock(&group->mutex) : + spin_trylock(&group->lock); + if (trylock) + break; /* OK */ + + /* re-lock for avoiding ABBA deadlock */ + snd_pcm_stream_unlock(substream); + snd_pcm_group_lock(group, nonatomic); + snd_pcm_stream_lock(substream); + + /* check the group again; the above opens a small race window */ + if (substream->group == group) + break; /* OK */ + /* group changed, try again */ + snd_pcm_group_unref(group, substream); + } + return group; +} + +/* + * Note: call with stream lock + */ +static int snd_pcm_action(const struct action_ops *ops, + struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_group *group; + int res; + + group = snd_pcm_stream_group_ref(substream); + if (group) + res = snd_pcm_action_group(ops, substream, state, true); + else + res = snd_pcm_action_single(ops, substream, state); + snd_pcm_group_unref(group, substream); + return res; +} + +/* + * Note: don't use any locks before + */ +static int snd_pcm_action_lock_irq(const struct action_ops *ops, + struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + int res; + + snd_pcm_stream_lock_irq(substream); + res = snd_pcm_action(ops, substream, state); + snd_pcm_stream_unlock_irq(substream); + return res; +} + +/* + */ +static int snd_pcm_action_nonatomic(const struct action_ops *ops, + struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + int res; + + /* Guarantee the group members won't change during non-atomic action */ + down_read(&snd_pcm_link_rwsem); + res = snd_pcm_buffer_access_lock(substream->runtime); + if (res < 0) + goto unlock; + if (snd_pcm_stream_linked(substream)) + res = snd_pcm_action_group(ops, substream, state, false); + else + res = snd_pcm_action_single(ops, substream, state); + snd_pcm_buffer_access_unlock(substream->runtime); + unlock: + up_read(&snd_pcm_link_rwsem); + return res; +} + +/* + * start callbacks + */ +static int snd_pcm_pre_start(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->state != SNDRV_PCM_STATE_PREPARED) + return -EBADFD; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + !snd_pcm_playback_data(substream)) + return -EPIPE; + runtime->trigger_tstamp_latched = false; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_start(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + int err; + + if (substream->runtime->trigger_master != substream) + return 0; + err = substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); + /* XRUN happened during the start */ + if (err == -EPIPE) + __snd_pcm_set_state(substream->runtime, SNDRV_PCM_STATE_XRUN); + return err; +} + +static void snd_pcm_undo_start(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + if (substream->runtime->trigger_master == substream) { + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); + substream->runtime->stop_operating = true; + } +} + +static void snd_pcm_post_start(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + runtime->hw_ptr_jiffies = jiffies; + runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / + runtime->rate; + __snd_pcm_set_state(runtime, state); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART); +} + +static const struct action_ops snd_pcm_action_start = { + .pre_action = snd_pcm_pre_start, + .do_action = snd_pcm_do_start, + .undo_action = snd_pcm_undo_start, + .post_action = snd_pcm_post_start +}; + +/** + * snd_pcm_start - start all linked streams + * @substream: the PCM substream instance + * + * Return: Zero if successful, or a negative error code. + * The stream lock must be acquired before calling this function. + */ +int snd_pcm_start(struct snd_pcm_substream *substream) +{ + return snd_pcm_action(&snd_pcm_action_start, substream, + SNDRV_PCM_STATE_RUNNING); +} + +/* take the stream lock and start the streams */ +static int snd_pcm_start_lock_irq(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, + SNDRV_PCM_STATE_RUNNING); +} + +/* + * stop callbacks + */ +static int snd_pcm_pre_stop(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_stop(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + if (substream->runtime->trigger_master == substream && + snd_pcm_running(substream)) { + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); + substream->runtime->stop_operating = true; + } + return 0; /* unconditionally stop all substreams */ +} + +static void snd_pcm_post_stop(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->state != state) { + snd_pcm_trigger_tstamp(substream); + __snd_pcm_set_state(runtime, state); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTOP); + } + wake_up(&runtime->sleep); + wake_up(&runtime->tsleep); +} + +static const struct action_ops snd_pcm_action_stop = { + .pre_action = snd_pcm_pre_stop, + .do_action = snd_pcm_do_stop, + .post_action = snd_pcm_post_stop +}; + +/** + * snd_pcm_stop - try to stop all running streams in the substream group + * @substream: the PCM substream instance + * @state: PCM state after stopping the stream + * + * The state of each stream is then changed to the given state unconditionally. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_pcm_stop(struct snd_pcm_substream *substream, snd_pcm_state_t state) +{ + return snd_pcm_action(&snd_pcm_action_stop, substream, state); +} +EXPORT_SYMBOL(snd_pcm_stop); + +/** + * snd_pcm_drain_done - stop the DMA only when the given stream is playback + * @substream: the PCM substream + * + * After stopping, the state is changed to SETUP. + * Unlike snd_pcm_stop(), this affects only the given stream. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_pcm_drain_done(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_single(&snd_pcm_action_stop, substream, + SNDRV_PCM_STATE_SETUP); +} + +/** + * snd_pcm_stop_xrun - stop the running streams as XRUN + * @substream: the PCM substream instance + * + * This stops the given running substream (and all linked substreams) as XRUN. + * Unlike snd_pcm_stop(), this function takes the substream lock by itself. + * + * Return: Zero if successful, or a negative error code. + */ +int snd_pcm_stop_xrun(struct snd_pcm_substream *substream) +{ + unsigned long flags; + + snd_pcm_stream_lock_irqsave(substream, flags); + if (substream->runtime && snd_pcm_running(substream)) + __snd_pcm_xrun(substream); + snd_pcm_stream_unlock_irqrestore(substream, flags); + return 0; +} +EXPORT_SYMBOL_GPL(snd_pcm_stop_xrun); + +/* + * pause callbacks: pass boolean (to start pause or resume) as state argument + */ +#define pause_pushed(state) (__force bool)(state) + +static int snd_pcm_pre_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_PAUSE)) + return -ENOSYS; + if (pause_pushed(state)) { + if (runtime->state != SNDRV_PCM_STATE_RUNNING) + return -EBADFD; + } else if (runtime->state != SNDRV_PCM_STATE_PAUSED) + return -EBADFD; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + if (substream->runtime->trigger_master != substream) + return 0; + /* The jiffies check in snd_pcm_update_hw_ptr*() is done by + * a delta between the current jiffies, this gives a large enough + * delta, effectively to skip the check once. + */ + substream->runtime->hw_ptr_jiffies = jiffies - HZ * 1000; + return substream->ops->trigger(substream, + pause_pushed(state) ? + SNDRV_PCM_TRIGGER_PAUSE_PUSH : + SNDRV_PCM_TRIGGER_PAUSE_RELEASE); +} + +static void snd_pcm_undo_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + if (substream->runtime->trigger_master == substream) + substream->ops->trigger(substream, + pause_pushed(state) ? + SNDRV_PCM_TRIGGER_PAUSE_RELEASE : + SNDRV_PCM_TRIGGER_PAUSE_PUSH); +} + +static void snd_pcm_post_pause(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + if (pause_pushed(state)) { + __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_PAUSED); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MPAUSE); + wake_up(&runtime->sleep); + wake_up(&runtime->tsleep); + } else { + __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_RUNNING); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MCONTINUE); + } +} + +static const struct action_ops snd_pcm_action_pause = { + .pre_action = snd_pcm_pre_pause, + .do_action = snd_pcm_do_pause, + .undo_action = snd_pcm_undo_pause, + .post_action = snd_pcm_post_pause +}; + +/* + * Push/release the pause for all linked streams. + */ +static int snd_pcm_pause(struct snd_pcm_substream *substream, bool push) +{ + return snd_pcm_action(&snd_pcm_action_pause, substream, + (__force snd_pcm_state_t)push); +} + +static int snd_pcm_pause_lock_irq(struct snd_pcm_substream *substream, + bool push) +{ + return snd_pcm_action_lock_irq(&snd_pcm_action_pause, substream, + (__force snd_pcm_state_t)push); +} + +#ifdef CONFIG_PM +/* suspend callback: state argument ignored */ + +static int snd_pcm_pre_suspend(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { + case SNDRV_PCM_STATE_SUSPENDED: + return -EBUSY; + /* unresumable PCM state; return -EBUSY for skipping suspend */ + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_DISCONNECTED: + return -EBUSY; + } + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_suspend(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->trigger_master != substream) + return 0; + if (! snd_pcm_running(substream)) + return 0; + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND); + runtime->stop_operating = true; + return 0; /* suspend unconditionally */ +} + +static void snd_pcm_post_suspend(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + runtime->suspended_state = runtime->state; + runtime->status->suspended_state = runtime->suspended_state; + __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_SUSPENDED); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSUSPEND); + wake_up(&runtime->sleep); + wake_up(&runtime->tsleep); +} + +static const struct action_ops snd_pcm_action_suspend = { + .pre_action = snd_pcm_pre_suspend, + .do_action = snd_pcm_do_suspend, + .post_action = snd_pcm_post_suspend +}; + +/* + * snd_pcm_suspend - trigger SUSPEND to all linked streams + * @substream: the PCM substream + * + * After this call, all streams are changed to SUSPENDED state. + * + * Return: Zero if successful, or a negative error code. + */ +static int snd_pcm_suspend(struct snd_pcm_substream *substream) +{ + int err; + unsigned long flags; + + snd_pcm_stream_lock_irqsave(substream, flags); + err = snd_pcm_action(&snd_pcm_action_suspend, substream, + ACTION_ARG_IGNORE); + snd_pcm_stream_unlock_irqrestore(substream, flags); + return err; +} + +/** + * snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm + * @pcm: the PCM instance + * + * After this call, all streams are changed to SUSPENDED state. + * + * Return: Zero if successful (or @pcm is %NULL), or a negative error code. + */ +int snd_pcm_suspend_all(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int stream, err = 0; + + if (! pcm) + return 0; + + for_each_pcm_substream(pcm, stream, substream) { + /* FIXME: the open/close code should lock this as well */ + if (!substream->runtime) + continue; + + /* + * Skip BE dai link PCM's that are internal and may + * not have their substream ops set. + */ + if (!substream->ops) + continue; + + err = snd_pcm_suspend(substream); + if (err < 0 && err != -EBUSY) + return err; + } + + for_each_pcm_substream(pcm, stream, substream) + snd_pcm_sync_stop(substream, false); + + return 0; +} +EXPORT_SYMBOL(snd_pcm_suspend_all); + +/* resume callbacks: state argument ignored */ + +static int snd_pcm_pre_resume(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (!(runtime->info & SNDRV_PCM_INFO_RESUME)) + return -ENOSYS; + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_resume(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (runtime->trigger_master != substream) + return 0; + /* DMA not running previously? */ + if (runtime->suspended_state != SNDRV_PCM_STATE_RUNNING && + (runtime->suspended_state != SNDRV_PCM_STATE_DRAINING || + substream->stream != SNDRV_PCM_STREAM_PLAYBACK)) + return 0; + return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_RESUME); +} + +static void snd_pcm_undo_resume(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + if (substream->runtime->trigger_master == substream && + snd_pcm_running(substream)) + substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_SUSPEND); +} + +static void snd_pcm_post_resume(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_trigger_tstamp(substream); + __snd_pcm_set_state(runtime, runtime->suspended_state); + snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MRESUME); +} + +static const struct action_ops snd_pcm_action_resume = { + .pre_action = snd_pcm_pre_resume, + .do_action = snd_pcm_do_resume, + .undo_action = snd_pcm_undo_resume, + .post_action = snd_pcm_post_resume +}; + +static int snd_pcm_resume(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_lock_irq(&snd_pcm_action_resume, substream, + ACTION_ARG_IGNORE); +} + +#else + +static int snd_pcm_resume(struct snd_pcm_substream *substream) +{ + return -ENOSYS; +} + +#endif /* CONFIG_PM */ + +/* + * xrun ioctl + * + * Change the RUNNING stream(s) to XRUN state. + */ +static int snd_pcm_xrun(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int result; + + snd_pcm_stream_lock_irq(substream); + switch (runtime->state) { + case SNDRV_PCM_STATE_XRUN: + result = 0; /* already there */ + break; + case SNDRV_PCM_STATE_RUNNING: + __snd_pcm_xrun(substream); + result = 0; + break; + default: + result = -EBADFD; + } + snd_pcm_stream_unlock_irq(substream); + return result; +} + +/* + * reset ioctl + */ +/* reset callbacks: state argument ignored */ +static int snd_pcm_pre_reset(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + case SNDRV_PCM_STATE_SUSPENDED: + return 0; + default: + return -EBADFD; + } +} + +static int snd_pcm_do_reset(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err = snd_pcm_ops_ioctl(substream, SNDRV_PCM_IOCTL1_RESET, NULL); + if (err < 0) + return err; + snd_pcm_stream_lock_irq(substream); + runtime->hw_ptr_base = 0; + runtime->hw_ptr_interrupt = runtime->status->hw_ptr - + runtime->status->hw_ptr % runtime->period_size; + runtime->silence_start = runtime->status->hw_ptr; + runtime->silence_filled = 0; + snd_pcm_stream_unlock_irq(substream); + return 0; +} + +static void snd_pcm_post_reset(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_stream_lock_irq(substream); + runtime->control->appl_ptr = runtime->status->hw_ptr; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + runtime->silence_size > 0) + snd_pcm_playback_silence(substream, ULONG_MAX); + snd_pcm_stream_unlock_irq(substream); +} + +static const struct action_ops snd_pcm_action_reset = { + .pre_action = snd_pcm_pre_reset, + .do_action = snd_pcm_do_reset, + .post_action = snd_pcm_post_reset +}; + +static int snd_pcm_reset(struct snd_pcm_substream *substream) +{ + return snd_pcm_action_nonatomic(&snd_pcm_action_reset, substream, + ACTION_ARG_IGNORE); +} + +/* + * prepare ioctl + */ +/* pass f_flags as state argument */ +static int snd_pcm_pre_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int f_flags = (__force int)state; + + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + if (snd_pcm_running(substream)) + return -EBUSY; + substream->f_flags = f_flags; + return 0; +} + +static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + int err; + snd_pcm_sync_stop(substream, true); + err = substream->ops->prepare(substream); + if (err < 0) + return err; + return snd_pcm_do_reset(substream, state); +} + +static void snd_pcm_post_prepare(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + runtime->control->appl_ptr = runtime->status->hw_ptr; + snd_pcm_set_state(substream, SNDRV_PCM_STATE_PREPARED); +} + +static const struct action_ops snd_pcm_action_prepare = { + .pre_action = snd_pcm_pre_prepare, + .do_action = snd_pcm_do_prepare, + .post_action = snd_pcm_post_prepare +}; + +/** + * snd_pcm_prepare - prepare the PCM substream to be triggerable + * @substream: the PCM substream instance + * @file: file to refer f_flags + * + * Return: Zero if successful, or a negative error code. + */ +static int snd_pcm_prepare(struct snd_pcm_substream *substream, + struct file *file) +{ + int f_flags; + + if (file) + f_flags = file->f_flags; + else + f_flags = substream->f_flags; + + snd_pcm_stream_lock_irq(substream); + switch (substream->runtime->state) { + case SNDRV_PCM_STATE_PAUSED: + snd_pcm_pause(substream, false); + fallthrough; + case SNDRV_PCM_STATE_SUSPENDED: + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + break; + } + snd_pcm_stream_unlock_irq(substream); + + return snd_pcm_action_nonatomic(&snd_pcm_action_prepare, + substream, + (__force snd_pcm_state_t)f_flags); +} + +/* + * drain ioctl + */ + +/* drain init callbacks: state argument ignored */ +static int snd_pcm_pre_drain_init(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_DISCONNECTED: + case SNDRV_PCM_STATE_SUSPENDED: + return -EBADFD; + } + runtime->trigger_master = substream; + return 0; +} + +static int snd_pcm_do_drain_init(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (runtime->state) { + case SNDRV_PCM_STATE_PREPARED: + /* start playback stream if possible */ + if (! snd_pcm_playback_empty(substream)) { + snd_pcm_do_start(substream, SNDRV_PCM_STATE_DRAINING); + snd_pcm_post_start(substream, SNDRV_PCM_STATE_DRAINING); + } else { + __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_SETUP); + } + break; + case SNDRV_PCM_STATE_RUNNING: + __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_DRAINING); + break; + case SNDRV_PCM_STATE_XRUN: + __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_SETUP); + break; + default: + break; + } + } else { + /* stop running stream */ + if (runtime->state == SNDRV_PCM_STATE_RUNNING) { + snd_pcm_state_t new_state; + + new_state = snd_pcm_capture_avail(runtime) > 0 ? + SNDRV_PCM_STATE_DRAINING : SNDRV_PCM_STATE_SETUP; + snd_pcm_do_stop(substream, new_state); + snd_pcm_post_stop(substream, new_state); + } + } + + if (runtime->state == SNDRV_PCM_STATE_DRAINING && + runtime->trigger_master == substream && + (runtime->hw.info & SNDRV_PCM_INFO_DRAIN_TRIGGER)) + return substream->ops->trigger(substream, + SNDRV_PCM_TRIGGER_DRAIN); + + return 0; +} + +static void snd_pcm_post_drain_init(struct snd_pcm_substream *substream, + snd_pcm_state_t state) +{ +} + +static const struct action_ops snd_pcm_action_drain_init = { + .pre_action = snd_pcm_pre_drain_init, + .do_action = snd_pcm_do_drain_init, + .post_action = snd_pcm_post_drain_init +}; + +/* + * Drain the stream(s). + * When the substream is linked, sync until the draining of all playback streams + * is finished. + * After this call, all streams are supposed to be either SETUP or DRAINING + * (capture only) state. + */ +static int snd_pcm_drain(struct snd_pcm_substream *substream, + struct file *file) +{ + struct snd_card *card; + struct snd_pcm_runtime *runtime; + struct snd_pcm_substream *s; + struct snd_pcm_group *group; + wait_queue_entry_t wait; + int result = 0; + int nonblock = 0; + + card = substream->pcm->card; + runtime = substream->runtime; + + if (runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + + if (file) { + if (file->f_flags & O_NONBLOCK) + nonblock = 1; + } else if (substream->f_flags & O_NONBLOCK) + nonblock = 1; + + snd_pcm_stream_lock_irq(substream); + /* resume pause */ + if (runtime->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, false); + + /* pre-start/stop - all running streams are changed to DRAINING state */ + result = snd_pcm_action(&snd_pcm_action_drain_init, substream, + ACTION_ARG_IGNORE); + if (result < 0) + goto unlock; + /* in non-blocking, we don't wait in ioctl but let caller poll */ + if (nonblock) { + result = -EAGAIN; + goto unlock; + } + + for (;;) { + long tout; + struct snd_pcm_runtime *to_check; + if (signal_pending(current)) { + result = -ERESTARTSYS; + break; + } + /* find a substream to drain */ + to_check = NULL; + group = snd_pcm_stream_group_ref(substream); + snd_pcm_group_for_each_entry(s, substream) { + if (s->stream != SNDRV_PCM_STREAM_PLAYBACK) + continue; + runtime = s->runtime; + if (runtime->state == SNDRV_PCM_STATE_DRAINING) { + to_check = runtime; + break; + } + } + snd_pcm_group_unref(group, substream); + if (!to_check) + break; /* all drained */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&to_check->sleep, &wait); + snd_pcm_stream_unlock_irq(substream); + if (runtime->no_period_wakeup) + tout = MAX_SCHEDULE_TIMEOUT; + else { + tout = 100; + if (runtime->rate) { + long t = runtime->buffer_size * 1100 / runtime->rate; + tout = max(t, tout); + } + tout = msecs_to_jiffies(tout); + } + tout = schedule_timeout(tout); + + snd_pcm_stream_lock_irq(substream); + group = snd_pcm_stream_group_ref(substream); + snd_pcm_group_for_each_entry(s, substream) { + if (s->runtime == to_check) { + remove_wait_queue(&to_check->sleep, &wait); + break; + } + } + snd_pcm_group_unref(group, substream); + + if (card->shutdown) { + result = -ENODEV; + break; + } + if (tout == 0) { + if (substream->runtime->state == SNDRV_PCM_STATE_SUSPENDED) + result = -ESTRPIPE; + else { + dev_dbg(substream->pcm->card->dev, + "playback drain timeout (DMA or IRQ trouble?)\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + result = -EIO; + } + break; + } + } + + unlock: + snd_pcm_stream_unlock_irq(substream); + + return result; +} + +/* + * drop ioctl + * + * Immediately put all linked substreams into SETUP state. + */ +static int snd_pcm_drop(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + int result = 0; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + + snd_pcm_stream_lock_irq(substream); + /* resume pause */ + if (runtime->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, false); + + snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + snd_pcm_stream_unlock_irq(substream); + + return result; +} + + +static bool is_pcm_file(struct file *file) +{ + struct inode *inode = file_inode(file); + struct snd_pcm *pcm; + unsigned int minor; + + if (!S_ISCHR(inode->i_mode) || imajor(inode) != snd_major) + return false; + minor = iminor(inode); + pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + if (!pcm) + pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE); + if (!pcm) + return false; + snd_card_unref(pcm->card); + return true; +} + +/* + * PCM link handling + */ +static int snd_pcm_link(struct snd_pcm_substream *substream, int fd) +{ + int res = 0; + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream1; + struct snd_pcm_group *group, *target_group; + bool nonatomic = substream->pcm->nonatomic; + struct fd f = fdget(fd); + + if (!f.file) + return -EBADFD; + if (!is_pcm_file(f.file)) { + res = -EBADFD; + goto _badf; + } + pcm_file = f.file->private_data; + substream1 = pcm_file->substream; + + if (substream == substream1) { + res = -EINVAL; + goto _badf; + } + + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) { + res = -ENOMEM; + goto _nolock; + } + snd_pcm_group_init(group); + + down_write(&snd_pcm_link_rwsem); + if (substream->runtime->state == SNDRV_PCM_STATE_OPEN || + substream->runtime->state != substream1->runtime->state || + substream->pcm->nonatomic != substream1->pcm->nonatomic) { + res = -EBADFD; + goto _end; + } + if (snd_pcm_stream_linked(substream1)) { + res = -EALREADY; + goto _end; + } + + snd_pcm_stream_lock_irq(substream); + if (!snd_pcm_stream_linked(substream)) { + snd_pcm_group_assign(substream, group); + group = NULL; /* assigned, don't free this one below */ + } + target_group = substream->group; + snd_pcm_stream_unlock_irq(substream); + + snd_pcm_group_lock_irq(target_group, nonatomic); + snd_pcm_stream_lock_nested(substream1); + snd_pcm_group_assign(substream1, target_group); + refcount_inc(&target_group->refs); + snd_pcm_stream_unlock(substream1); + snd_pcm_group_unlock_irq(target_group, nonatomic); + _end: + up_write(&snd_pcm_link_rwsem); + _nolock: + kfree(group); + _badf: + fdput(f); + return res; +} + +static void relink_to_local(struct snd_pcm_substream *substream) +{ + snd_pcm_stream_lock_nested(substream); + snd_pcm_group_assign(substream, &substream->self_group); + snd_pcm_stream_unlock(substream); +} + +static int snd_pcm_unlink(struct snd_pcm_substream *substream) +{ + struct snd_pcm_group *group; + bool nonatomic = substream->pcm->nonatomic; + bool do_free = false; + int res = 0; + + down_write(&snd_pcm_link_rwsem); + + if (!snd_pcm_stream_linked(substream)) { + res = -EALREADY; + goto _end; + } + + group = substream->group; + snd_pcm_group_lock_irq(group, nonatomic); + + relink_to_local(substream); + refcount_dec(&group->refs); + + /* detach the last stream, too */ + if (list_is_singular(&group->substreams)) { + relink_to_local(list_first_entry(&group->substreams, + struct snd_pcm_substream, + link_list)); + do_free = refcount_dec_and_test(&group->refs); + } + + snd_pcm_group_unlock_irq(group, nonatomic); + if (do_free) + kfree(group); + + _end: + up_write(&snd_pcm_link_rwsem); + return res; +} + +/* + * hw configurator + */ +static int snd_pcm_hw_rule_mul(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_mul(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_div(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_div(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_muldivk(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_muldivk(hw_param_interval_c(params, rule->deps[0]), + hw_param_interval_c(params, rule->deps[1]), + (unsigned long) rule->private, &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_mulkdiv(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_interval_mulkdiv(hw_param_interval_c(params, rule->deps[0]), + (unsigned long) rule->private, + hw_param_interval_c(params, rule->deps[1]), &t); + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + snd_pcm_format_t k; + const struct snd_interval *i = + hw_param_interval_c(params, rule->deps[0]); + struct snd_mask m; + struct snd_mask *mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_any(&m); + pcm_for_each_format(k) { + int bits; + if (!snd_mask_test_format(mask, k)) + continue; + bits = snd_pcm_format_physical_width(k); + if (bits <= 0) + continue; /* ignore invalid formats */ + if ((unsigned)bits < i->min || (unsigned)bits > i->max) + snd_mask_reset(&m, (__force unsigned)k); + } + return snd_mask_refine(mask, &m); +} + +static int snd_pcm_hw_rule_sample_bits(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + snd_pcm_format_t k; + + t.min = UINT_MAX; + t.max = 0; + t.openmin = 0; + t.openmax = 0; + pcm_for_each_format(k) { + int bits; + if (!snd_mask_test_format(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT), k)) + continue; + bits = snd_pcm_format_physical_width(k); + if (bits <= 0) + continue; /* ignore invalid formats */ + if (t.min > (unsigned)bits) + t.min = bits; + if (t.max < (unsigned)bits) + t.max = bits; + } + t.integer = 1; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +#if SNDRV_PCM_RATE_5512 != 1 << 0 || SNDRV_PCM_RATE_192000 != 1 << 12 +#error "Change this table" +#endif + +static const unsigned int rates[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, + 48000, 64000, 88200, 96000, 176400, 192000, 352800, 384000 +}; + +const struct snd_pcm_hw_constraint_list snd_pcm_known_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, +}; + +static int snd_pcm_hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_hardware *hw = rule->private; + return snd_interval_list(hw_param_interval(params, rule->var), + snd_pcm_known_rates.count, + snd_pcm_known_rates.list, hw->rates); +} + +static int snd_pcm_hw_rule_buffer_bytes_max(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval t; + struct snd_pcm_substream *substream = rule->private; + t.min = 0; + t.max = substream->buffer_bytes_max; + t.openmin = 0; + t.openmax = 0; + t.integer = 1; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int snd_pcm_hw_constraints_init(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints; + int k, err; + + for (k = SNDRV_PCM_HW_PARAM_FIRST_MASK; k <= SNDRV_PCM_HW_PARAM_LAST_MASK; k++) { + snd_mask_any(constrs_mask(constrs, k)); + } + + for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) { + snd_interval_any(constrs_interval(constrs, k)); + } + + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_CHANNELS)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_SIZE)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_BUFFER_BYTES)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)); + snd_interval_setinteger(constrs_interval(constrs, SNDRV_PCM_HW_PARAM_FRAME_BITS)); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT, + snd_pcm_hw_rule_format, NULL, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + snd_pcm_hw_rule_sample_bits, NULL, + SNDRV_PCM_HW_PARAM_FORMAT, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mul, NULL, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_FRAME_BITS, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_FRAME_BITS, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_TIME, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_BUFFER_TIME, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIODS, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_div, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + snd_pcm_hw_rule_muldivk, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_mul, NULL, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_PERIODS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_mulkdiv, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + snd_pcm_hw_rule_muldivk, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_TIME, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + snd_pcm_hw_rule_muldivk, (void*) 8, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + snd_pcm_hw_rule_muldivk, (void*) 8, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_FRAME_BITS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_TIME, + snd_pcm_hw_rule_mulkdiv, (void*) 1000000, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + return 0; +} + +static int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + int err; + unsigned int mask = 0; + + if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) + mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_RW_INTERLEAVED); + if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) + mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_RW_NONINTERLEAVED); + if (hw_support_mmap(substream)) { + if (hw->info & SNDRV_PCM_INFO_INTERLEAVED) + mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_MMAP_INTERLEAVED); + if (hw->info & SNDRV_PCM_INFO_NONINTERLEAVED) + mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED); + if (hw->info & SNDRV_PCM_INFO_COMPLEX) + mask |= PARAM_MASK_BIT(SNDRV_PCM_ACCESS_MMAP_COMPLEX); + } + err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_SUBFORMAT, + PARAM_MASK_BIT(SNDRV_PCM_SUBFORMAT_STD)); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, + hw->channels_min, hw->channels_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, + hw->rate_min, hw->rate_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + hw->period_bytes_min, hw->period_bytes_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS, + hw->periods_min, hw->periods_max); + if (err < 0) + return err; + + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + hw->period_bytes_min, hw->buffer_bytes_max); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + snd_pcm_hw_rule_buffer_bytes_max, substream, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, -1); + if (err < 0) + return err; + + /* FIXME: remove */ + if (runtime->dma_bytes) { + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 0, runtime->dma_bytes); + if (err < 0) + return err; + } + + if (!(hw->rates & (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))) { + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_pcm_hw_rule_rate, hw, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + } + + /* FIXME: this belong to lowlevel */ + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); + + return 0; +} + +static void pcm_release_private(struct snd_pcm_substream *substream) +{ + if (snd_pcm_stream_linked(substream)) + snd_pcm_unlink(substream); +} + +void snd_pcm_release_substream(struct snd_pcm_substream *substream) +{ + substream->ref_count--; + if (substream->ref_count > 0) + return; + + snd_pcm_drop(substream); + if (substream->hw_opened) { + if (substream->runtime->state != SNDRV_PCM_STATE_OPEN) + do_hw_free(substream); + substream->ops->close(substream); + substream->hw_opened = 0; + } + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + if (substream->pcm_release) { + substream->pcm_release(substream); + substream->pcm_release = NULL; + } + snd_pcm_detach_substream(substream); +} +EXPORT_SYMBOL(snd_pcm_release_substream); + +int snd_pcm_open_substream(struct snd_pcm *pcm, int stream, + struct file *file, + struct snd_pcm_substream **rsubstream) +{ + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_attach_substream(pcm, stream, file, &substream); + if (err < 0) + return err; + if (substream->ref_count > 1) { + *rsubstream = substream; + return 0; + } + + err = snd_pcm_hw_constraints_init(substream); + if (err < 0) { + pcm_dbg(pcm, "snd_pcm_hw_constraints_init failed\n"); + goto error; + } + + err = substream->ops->open(substream); + if (err < 0) + goto error; + + substream->hw_opened = 1; + + err = snd_pcm_hw_constraints_complete(substream); + if (err < 0) { + pcm_dbg(pcm, "snd_pcm_hw_constraints_complete failed\n"); + goto error; + } + + /* automatically set EXPLICIT_SYNC flag in the managed mode whenever + * the DMA buffer requires it + */ + if (substream->managed_buffer_alloc && + substream->dma_buffer.dev.need_sync) + substream->runtime->hw.info |= SNDRV_PCM_INFO_EXPLICIT_SYNC; + + *rsubstream = substream; + return 0; + + error: + snd_pcm_release_substream(substream); + return err; +} +EXPORT_SYMBOL(snd_pcm_open_substream); + +static int snd_pcm_open_file(struct file *file, + struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + int err; + + err = snd_pcm_open_substream(pcm, stream, file, &substream); + if (err < 0) + return err; + + pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL); + if (pcm_file == NULL) { + snd_pcm_release_substream(substream); + return -ENOMEM; + } + pcm_file->substream = substream; + if (substream->ref_count == 1) + substream->pcm_release = pcm_release_private; + file->private_data = pcm_file; + + return 0; +} + +static int snd_pcm_playback_open(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + int err = nonseekable_open(inode, file); + if (err < 0) + return err; + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); + if (pcm) + snd_card_unref(pcm->card); + return err; +} + +static int snd_pcm_capture_open(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + int err = nonseekable_open(inode, file); + if (err < 0) + return err; + pcm = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE); + err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); + if (pcm) + snd_card_unref(pcm->card); + return err; +} + +static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) +{ + int err; + wait_queue_entry_t wait; + + if (pcm == NULL) { + err = -ENODEV; + goto __error1; + } + err = snd_card_file_add(pcm->card, file); + if (err < 0) + goto __error1; + if (!try_module_get(pcm->card->module)) { + err = -EFAULT; + goto __error2; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&pcm->open_wait, &wait); + mutex_lock(&pcm->open_mutex); + while (1) { + err = snd_pcm_open_file(file, pcm, stream); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&pcm->open_mutex); + schedule(); + mutex_lock(&pcm->open_mutex); + if (pcm->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&pcm->open_wait, &wait); + mutex_unlock(&pcm->open_mutex); + if (err < 0) + goto __error; + return err; + + __error: + module_put(pcm->card->module); + __error2: + snd_card_file_remove(pcm->card, file); + __error1: + return err; +} + +static int snd_pcm_release(struct inode *inode, struct file *file) +{ + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + struct snd_pcm_file *pcm_file; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (snd_BUG_ON(!substream)) + return -ENXIO; + pcm = substream->pcm; + + /* block until the device gets woken up as it may touch the hardware */ + snd_power_wait(pcm->card); + + mutex_lock(&pcm->open_mutex); + snd_pcm_release_substream(substream); + kfree(pcm_file); + mutex_unlock(&pcm->open_mutex); + wake_up(&pcm->open_wait); + module_put(pcm->card->module); + snd_card_file_remove(pcm->card, file); + return 0; +} + +/* check and update PCM state; return 0 or a negative error + * call this inside PCM lock + */ +static int do_pcm_hwsync(struct snd_pcm_substream *substream) +{ + switch (substream->runtime->state) { + case SNDRV_PCM_STATE_DRAINING: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return -EBADFD; + fallthrough; + case SNDRV_PCM_STATE_RUNNING: + return snd_pcm_update_hw_ptr(substream); + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + return 0; + case SNDRV_PCM_STATE_SUSPENDED: + return -ESTRPIPE; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + return -EBADFD; + } +} + +/* increase the appl_ptr; returns the processed frames or a negative error */ +static snd_pcm_sframes_t forward_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames, + snd_pcm_sframes_t avail) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + int ret; + + if (avail <= 0) + return 0; + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + appl_ptr = runtime->control->appl_ptr + frames; + if (appl_ptr >= (snd_pcm_sframes_t)runtime->boundary) + appl_ptr -= runtime->boundary; + ret = pcm_lib_apply_appl_ptr(substream, appl_ptr); + return ret < 0 ? ret : frames; +} + +/* decrease the appl_ptr; returns the processed frames or zero for error */ +static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames, + snd_pcm_sframes_t avail) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t appl_ptr; + int ret; + + if (avail <= 0) + return 0; + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + appl_ptr = runtime->control->appl_ptr - frames; + if (appl_ptr < 0) + appl_ptr += runtime->boundary; + ret = pcm_lib_apply_appl_ptr(substream, appl_ptr); + /* NOTE: we return zero for errors because PulseAudio gets depressed + * upon receiving an error from rewind ioctl and stops processing + * any longer. Returning zero means that no rewind is done, so + * it's not absolutely wrong to answer like that. + */ + return ret < 0 ? 0 : frames; +} + +static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames) +{ + snd_pcm_sframes_t ret; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + ret = do_pcm_hwsync(substream); + if (!ret) + ret = rewind_appl_ptr(substream, frames, + snd_pcm_hw_avail(substream)); + snd_pcm_stream_unlock_irq(substream); + if (ret >= 0) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + return ret; +} + +static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, + snd_pcm_uframes_t frames) +{ + snd_pcm_sframes_t ret; + + if (frames == 0) + return 0; + + snd_pcm_stream_lock_irq(substream); + ret = do_pcm_hwsync(substream); + if (!ret) + ret = forward_appl_ptr(substream, frames, + snd_pcm_avail(substream)); + snd_pcm_stream_unlock_irq(substream); + if (ret >= 0) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + return ret; +} + +static int snd_pcm_delay(struct snd_pcm_substream *substream, + snd_pcm_sframes_t *delay) +{ + int err; + + snd_pcm_stream_lock_irq(substream); + err = do_pcm_hwsync(substream); + if (delay && !err) + *delay = snd_pcm_calc_delay(substream); + snd_pcm_stream_unlock_irq(substream); + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); + + return err; +} + +static inline int snd_pcm_hwsync(struct snd_pcm_substream *substream) +{ + return snd_pcm_delay(substream, NULL); +} + +static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, + struct snd_pcm_sync_ptr __user *_sync_ptr) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_sync_ptr sync_ptr; + volatile struct snd_pcm_mmap_status *status; + volatile struct snd_pcm_mmap_control *control; + int err; + + memset(&sync_ptr, 0, sizeof(sync_ptr)); + if (get_user(sync_ptr.flags, (unsigned __user *)&(_sync_ptr->flags))) + return -EFAULT; + if (copy_from_user(&sync_ptr.c.control, &(_sync_ptr->c.control), sizeof(struct snd_pcm_mmap_control))) + return -EFAULT; + status = runtime->status; + control = runtime->control; + if (sync_ptr.flags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + snd_pcm_stream_lock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) { + err = pcm_lib_apply_appl_ptr(substream, + sync_ptr.c.control.appl_ptr); + if (err < 0) { + snd_pcm_stream_unlock_irq(substream); + return err; + } + } else { + sync_ptr.c.control.appl_ptr = control->appl_ptr; + } + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = sync_ptr.c.control.avail_min; + else + sync_ptr.c.control.avail_min = control->avail_min; + sync_ptr.s.status.state = status->state; + sync_ptr.s.status.hw_ptr = status->hw_ptr; + sync_ptr.s.status.tstamp = status->tstamp; + sync_ptr.s.status.suspended_state = status->suspended_state; + sync_ptr.s.status.audio_tstamp = status->audio_tstamp; + snd_pcm_stream_unlock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr))) + return -EFAULT; + return 0; +} + +struct snd_pcm_mmap_status32 { + snd_pcm_state_t state; + s32 pad1; + u32 hw_ptr; + s32 tstamp_sec; + s32 tstamp_nsec; + snd_pcm_state_t suspended_state; + s32 audio_tstamp_sec; + s32 audio_tstamp_nsec; +} __attribute__((packed)); + +struct snd_pcm_mmap_control32 { + u32 appl_ptr; + u32 avail_min; +}; + +struct snd_pcm_sync_ptr32 { + u32 flags; + union { + struct snd_pcm_mmap_status32 status; + unsigned char reserved[64]; + } s; + union { + struct snd_pcm_mmap_control32 control; + unsigned char reserved[64]; + } c; +} __attribute__((packed)); + +/* recalcuate the boundary within 32bit */ +static snd_pcm_uframes_t recalculate_boundary(struct snd_pcm_runtime *runtime) +{ + snd_pcm_uframes_t boundary; + + if (! runtime->buffer_size) + return 0; + boundary = runtime->buffer_size; + while (boundary * 2 <= 0x7fffffffUL - runtime->buffer_size) + boundary *= 2; + return boundary; +} + +static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream, + struct snd_pcm_sync_ptr32 __user *src) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + volatile struct snd_pcm_mmap_status *status; + volatile struct snd_pcm_mmap_control *control; + u32 sflags; + struct snd_pcm_mmap_control scontrol; + struct snd_pcm_mmap_status sstatus; + snd_pcm_uframes_t boundary; + int err; + + if (snd_BUG_ON(!runtime)) + return -EINVAL; + + if (get_user(sflags, &src->flags) || + get_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + get_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + if (sflags & SNDRV_PCM_SYNC_PTR_HWSYNC) { + err = snd_pcm_hwsync(substream); + if (err < 0) + return err; + } + status = runtime->status; + control = runtime->control; + boundary = recalculate_boundary(runtime); + if (! boundary) + boundary = 0x7fffffff; + snd_pcm_stream_lock_irq(substream); + /* FIXME: we should consider the boundary for the sync from app */ + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) { + err = pcm_lib_apply_appl_ptr(substream, + scontrol.appl_ptr); + if (err < 0) { + snd_pcm_stream_unlock_irq(substream); + return err; + } + } else + scontrol.appl_ptr = control->appl_ptr % boundary; + if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) + control->avail_min = scontrol.avail_min; + else + scontrol.avail_min = control->avail_min; + sstatus.state = status->state; + sstatus.hw_ptr = status->hw_ptr % boundary; + sstatus.tstamp = status->tstamp; + sstatus.suspended_state = status->suspended_state; + sstatus.audio_tstamp = status->audio_tstamp; + snd_pcm_stream_unlock_irq(substream); + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + if (put_user(sstatus.state, &src->s.status.state) || + put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || + put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) || + put_user(sstatus.tstamp.tv_nsec, &src->s.status.tstamp_nsec) || + put_user(sstatus.suspended_state, &src->s.status.suspended_state) || + put_user(sstatus.audio_tstamp.tv_sec, &src->s.status.audio_tstamp_sec) || + put_user(sstatus.audio_tstamp.tv_nsec, &src->s.status.audio_tstamp_nsec) || + put_user(scontrol.appl_ptr, &src->c.control.appl_ptr) || + put_user(scontrol.avail_min, &src->c.control.avail_min)) + return -EFAULT; + + return 0; +} +#define __SNDRV_PCM_IOCTL_SYNC_PTR32 _IOWR('A', 0x23, struct snd_pcm_sync_ptr32) + +static int snd_pcm_tstamp(struct snd_pcm_substream *substream, int __user *_arg) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int arg; + + if (get_user(arg, _arg)) + return -EFAULT; + if (arg < 0 || arg > SNDRV_PCM_TSTAMP_TYPE_LAST) + return -EINVAL; + runtime->tstamp_type = arg; + return 0; +} + +static int snd_pcm_xferi_frames_ioctl(struct snd_pcm_substream *substream, + struct snd_xferi __user *_xferi) +{ + struct snd_xferi xferi; + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_sframes_t result; + + if (runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (put_user(0, &_xferi->result)) + return -EFAULT; + if (copy_from_user(&xferi, _xferi, sizeof(xferi))) + return -EFAULT; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames); + else + result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames); + if (put_user(result, &_xferi->result)) + return -EFAULT; + return result < 0 ? result : 0; +} + +static int snd_pcm_xfern_frames_ioctl(struct snd_pcm_substream *substream, + struct snd_xfern __user *_xfern) +{ + struct snd_xfern xfern; + struct snd_pcm_runtime *runtime = substream->runtime; + void *bufs; + snd_pcm_sframes_t result; + + if (runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (runtime->channels > 128) + return -EINVAL; + if (put_user(0, &_xfern->result)) + return -EFAULT; + if (copy_from_user(&xfern, _xfern, sizeof(xfern))) + return -EFAULT; + + bufs = memdup_user(xfern.bufs, sizeof(void *) * runtime->channels); + if (IS_ERR(bufs)) + return PTR_ERR(bufs); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + result = snd_pcm_lib_writev(substream, bufs, xfern.frames); + else + result = snd_pcm_lib_readv(substream, bufs, xfern.frames); + kfree(bufs); + if (put_user(result, &_xfern->result)) + return -EFAULT; + return result < 0 ? result : 0; +} + +static int snd_pcm_rewind_ioctl(struct snd_pcm_substream *substream, + snd_pcm_uframes_t __user *_frames) +{ + snd_pcm_uframes_t frames; + snd_pcm_sframes_t result; + + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_rewind(substream, frames); + if (put_user(result, _frames)) + return -EFAULT; + return result < 0 ? result : 0; +} + +static int snd_pcm_forward_ioctl(struct snd_pcm_substream *substream, + snd_pcm_uframes_t __user *_frames) +{ + snd_pcm_uframes_t frames; + snd_pcm_sframes_t result; + + if (get_user(frames, _frames)) + return -EFAULT; + if (put_user(0, _frames)) + return -EFAULT; + result = snd_pcm_forward(substream, frames); + if (put_user(result, _frames)) + return -EFAULT; + return result < 0 ? result : 0; +} + +static int snd_pcm_common_ioctl(struct file *file, + struct snd_pcm_substream *substream, + unsigned int cmd, void __user *arg) +{ + struct snd_pcm_file *pcm_file = file->private_data; + int res; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + + if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + + res = snd_power_wait(substream->pcm->card); + if (res < 0) + return res; + + switch (cmd) { + case SNDRV_PCM_IOCTL_PVERSION: + return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0; + case SNDRV_PCM_IOCTL_INFO: + return snd_pcm_info_user(substream, arg); + case SNDRV_PCM_IOCTL_TSTAMP: /* just for compatibility */ + return 0; + case SNDRV_PCM_IOCTL_TTSTAMP: + return snd_pcm_tstamp(substream, arg); + case SNDRV_PCM_IOCTL_USER_PVERSION: + if (get_user(pcm_file->user_pversion, + (unsigned int __user *)arg)) + return -EFAULT; + return 0; + case SNDRV_PCM_IOCTL_HW_REFINE: + return snd_pcm_hw_refine_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_PARAMS: + return snd_pcm_hw_params_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_FREE: + return snd_pcm_hw_free(substream); + case SNDRV_PCM_IOCTL_SW_PARAMS: + return snd_pcm_sw_params_user(substream, arg); + case SNDRV_PCM_IOCTL_STATUS32: + return snd_pcm_status_user32(substream, arg, false); + case SNDRV_PCM_IOCTL_STATUS_EXT32: + return snd_pcm_status_user32(substream, arg, true); + case SNDRV_PCM_IOCTL_STATUS64: + return snd_pcm_status_user64(substream, arg, false); + case SNDRV_PCM_IOCTL_STATUS_EXT64: + return snd_pcm_status_user64(substream, arg, true); + case SNDRV_PCM_IOCTL_CHANNEL_INFO: + return snd_pcm_channel_info_user(substream, arg); + case SNDRV_PCM_IOCTL_PREPARE: + return snd_pcm_prepare(substream, file); + case SNDRV_PCM_IOCTL_RESET: + return snd_pcm_reset(substream); + case SNDRV_PCM_IOCTL_START: + return snd_pcm_start_lock_irq(substream); + case SNDRV_PCM_IOCTL_LINK: + return snd_pcm_link(substream, (int)(unsigned long) arg); + case SNDRV_PCM_IOCTL_UNLINK: + return snd_pcm_unlink(substream); + case SNDRV_PCM_IOCTL_RESUME: + return snd_pcm_resume(substream); + case SNDRV_PCM_IOCTL_XRUN: + return snd_pcm_xrun(substream); + case SNDRV_PCM_IOCTL_HWSYNC: + return snd_pcm_hwsync(substream); + case SNDRV_PCM_IOCTL_DELAY: + { + snd_pcm_sframes_t delay = 0; + snd_pcm_sframes_t __user *res = arg; + int err; + + err = snd_pcm_delay(substream, &delay); + if (err) + return err; + if (put_user(delay, res)) + return -EFAULT; + return 0; + } + case __SNDRV_PCM_IOCTL_SYNC_PTR32: + return snd_pcm_ioctl_sync_ptr_compat(substream, arg); + case __SNDRV_PCM_IOCTL_SYNC_PTR64: + return snd_pcm_sync_ptr(substream, arg); +#ifdef CONFIG_SND_SUPPORT_OLD_API + case SNDRV_PCM_IOCTL_HW_REFINE_OLD: + return snd_pcm_hw_refine_old_user(substream, arg); + case SNDRV_PCM_IOCTL_HW_PARAMS_OLD: + return snd_pcm_hw_params_old_user(substream, arg); +#endif + case SNDRV_PCM_IOCTL_DRAIN: + return snd_pcm_drain(substream, file); + case SNDRV_PCM_IOCTL_DROP: + return snd_pcm_drop(substream); + case SNDRV_PCM_IOCTL_PAUSE: + return snd_pcm_pause_lock_irq(substream, (unsigned long)arg); + case SNDRV_PCM_IOCTL_WRITEI_FRAMES: + case SNDRV_PCM_IOCTL_READI_FRAMES: + return snd_pcm_xferi_frames_ioctl(substream, arg); + case SNDRV_PCM_IOCTL_WRITEN_FRAMES: + case SNDRV_PCM_IOCTL_READN_FRAMES: + return snd_pcm_xfern_frames_ioctl(substream, arg); + case SNDRV_PCM_IOCTL_REWIND: + return snd_pcm_rewind_ioctl(substream, arg); + case SNDRV_PCM_IOCTL_FORWARD: + return snd_pcm_forward_ioctl(substream, arg); + } + pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd); + return -ENOTTY; +} + +static long snd_pcm_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_pcm_file *pcm_file; + + pcm_file = file->private_data; + + if (((cmd >> 8) & 0xff) != 'A') + return -ENOTTY; + + return snd_pcm_common_ioctl(file, pcm_file->substream, cmd, + (void __user *)arg); +} + +/** + * snd_pcm_kernel_ioctl - Execute PCM ioctl in the kernel-space + * @substream: PCM substream + * @cmd: IOCTL cmd + * @arg: IOCTL argument + * + * The function is provided primarily for OSS layer and USB gadget drivers, + * and it allows only the limited set of ioctls (hw_params, sw_params, + * prepare, start, drain, drop, forward). + * + * Return: zero if successful, or a negative error code + */ +int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + snd_pcm_uframes_t *frames = arg; + snd_pcm_sframes_t result; + + if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + + switch (cmd) { + case SNDRV_PCM_IOCTL_FORWARD: + { + /* provided only for OSS; capture-only and no value returned */ + if (substream->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + result = snd_pcm_forward(substream, *frames); + return result < 0 ? result : 0; + } + case SNDRV_PCM_IOCTL_HW_PARAMS: + return snd_pcm_hw_params(substream, arg); + case SNDRV_PCM_IOCTL_SW_PARAMS: + return snd_pcm_sw_params(substream, arg); + case SNDRV_PCM_IOCTL_PREPARE: + return snd_pcm_prepare(substream, NULL); + case SNDRV_PCM_IOCTL_START: + return snd_pcm_start_lock_irq(substream); + case SNDRV_PCM_IOCTL_DRAIN: + return snd_pcm_drain(substream, NULL); + case SNDRV_PCM_IOCTL_DROP: + return snd_pcm_drop(substream); + case SNDRV_PCM_IOCTL_DELAY: + return snd_pcm_delay(substream, frames); + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(snd_pcm_kernel_ioctl); + +static ssize_t snd_pcm_read(struct file *file, char __user *buf, size_t count, + loff_t * offset) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + if (!frame_aligned(runtime, count)) + return -EINVAL; + count = bytes_to_frames(runtime, count); + result = snd_pcm_lib_read(substream, buf, count); + if (result > 0) + result = frames_to_bytes(runtime, result); + return result; +} + +static ssize_t snd_pcm_write(struct file *file, const char __user *buf, + size_t count, loff_t * offset) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + if (!frame_aligned(runtime, count)) + return -EINVAL; + count = bytes_to_frames(runtime, count); + result = snd_pcm_lib_write(substream, buf, count); + if (result > 0) + result = frames_to_bytes(runtime, result); + return result; +} + +static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + unsigned long i; + void __user **bufs; + snd_pcm_uframes_t frames; + const struct iovec *iov = iter_iov(to); + + pcm_file = iocb->ki_filp->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + if (!to->user_backed) + return -EINVAL; + if (to->nr_segs > 1024 || to->nr_segs != runtime->channels) + return -EINVAL; + if (!frame_aligned(runtime, iov->iov_len)) + return -EINVAL; + frames = bytes_to_samples(runtime, iov->iov_len); + bufs = kmalloc_array(to->nr_segs, sizeof(void *), GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < to->nr_segs; ++i) { + bufs[i] = iov->iov_base; + iov++; + } + result = snd_pcm_lib_readv(substream, bufs, frames); + if (result > 0) + result = frames_to_bytes(runtime, result); + kfree(bufs); + return result; +} + +static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + snd_pcm_sframes_t result; + unsigned long i; + void __user **bufs; + snd_pcm_uframes_t frames; + const struct iovec *iov = iter_iov(from); + + pcm_file = iocb->ki_filp->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + if (!from->user_backed) + return -EINVAL; + if (from->nr_segs > 128 || from->nr_segs != runtime->channels || + !frame_aligned(runtime, iov->iov_len)) + return -EINVAL; + frames = bytes_to_samples(runtime, iov->iov_len); + bufs = kmalloc_array(from->nr_segs, sizeof(void *), GFP_KERNEL); + if (bufs == NULL) + return -ENOMEM; + for (i = 0; i < from->nr_segs; ++i) { + bufs[i] = iov->iov_base; + iov++; + } + result = snd_pcm_lib_writev(substream, bufs, frames); + if (result > 0) + result = frames_to_bytes(runtime, result); + kfree(bufs); + return result; +} + +static __poll_t snd_pcm_poll(struct file *file, poll_table *wait) +{ + struct snd_pcm_file *pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + __poll_t mask, ok; + snd_pcm_uframes_t avail; + + pcm_file = file->private_data; + + substream = pcm_file->substream; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ok = EPOLLOUT | EPOLLWRNORM; + else + ok = EPOLLIN | EPOLLRDNORM; + if (PCM_RUNTIME_CHECK(substream)) + return ok | EPOLLERR; + + runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return ok | EPOLLERR; + + poll_wait(file, &runtime->sleep, wait); + + mask = 0; + snd_pcm_stream_lock_irq(substream); + avail = snd_pcm_avail(substream); + switch (runtime->state) { + case SNDRV_PCM_STATE_RUNNING: + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_PAUSED: + if (avail >= runtime->control->avail_min) + mask = ok; + break; + case SNDRV_PCM_STATE_DRAINING: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + mask = ok; + if (!avail) + mask |= EPOLLERR; + } + break; + default: + mask = ok | EPOLLERR; + break; + } + snd_pcm_stream_unlock_irq(substream); + return mask; +} + +/* + * mmap support + */ + +/* + * Only on coherent architectures, we can mmap the status and the control records + * for effcient data transfer. On others, we have to use HWSYNC ioctl... + */ +#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_ALPHA) +/* + * mmap status record + */ +static vm_fault_t snd_pcm_mmap_status_fault(struct vm_fault *vmf) +{ + struct snd_pcm_substream *substream = vmf->vma->vm_private_data; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; + vmf->page = virt_to_page(runtime->status); + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct snd_pcm_vm_ops_status = +{ + .fault = snd_pcm_mmap_status_fault, +}; + +static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + long size; + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + size = area->vm_end - area->vm_start; + if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_status))) + return -EINVAL; + area->vm_ops = &snd_pcm_vm_ops_status; + area->vm_private_data = substream; + vm_flags_mod(area, VM_DONTEXPAND | VM_DONTDUMP, + VM_WRITE | VM_MAYWRITE); + + return 0; +} + +/* + * mmap control record + */ +static vm_fault_t snd_pcm_mmap_control_fault(struct vm_fault *vmf) +{ + struct snd_pcm_substream *substream = vmf->vma->vm_private_data; + struct snd_pcm_runtime *runtime; + + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; + vmf->page = virt_to_page(runtime->control); + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct snd_pcm_vm_ops_control = +{ + .fault = snd_pcm_mmap_control_fault, +}; + +static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + long size; + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + size = area->vm_end - area->vm_start; + if (size != PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control))) + return -EINVAL; + area->vm_ops = &snd_pcm_vm_ops_control; + area->vm_private_data = substream; + vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP); + return 0; +} + +static bool pcm_status_mmap_allowed(struct snd_pcm_file *pcm_file) +{ + /* If drivers require the explicit sync (typically for non-coherent + * pages), we have to disable the mmap of status and control data + * to enforce the control via SYNC_PTR ioctl. + */ + if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + return false; + /* See pcm_control_mmap_allowed() below. + * Since older alsa-lib requires both status and control mmaps to be + * coupled, we have to disable the status mmap for old alsa-lib, too. + */ + if (pcm_file->user_pversion < SNDRV_PROTOCOL_VERSION(2, 0, 14) && + (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR)) + return false; + return true; +} + +static bool pcm_control_mmap_allowed(struct snd_pcm_file *pcm_file) +{ + if (pcm_file->no_compat_mmap) + return false; + /* see above */ + if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + return false; + /* Disallow the control mmap when SYNC_APPLPTR flag is set; + * it enforces the user-space to fall back to snd_pcm_sync_ptr(), + * thus it effectively assures the manual update of appl_ptr. + */ + if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_SYNC_APPLPTR) + return false; + return true; +} + +#else /* ! coherent mmap */ +/* + * don't support mmap for status and control records. + */ +#define pcm_status_mmap_allowed(pcm_file) false +#define pcm_control_mmap_allowed(pcm_file) false + +static int snd_pcm_mmap_status(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + return -ENXIO; +} +static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + return -ENXIO; +} +#endif /* coherent mmap */ + +/* + * fault callback for mmapping a RAM page + */ +static vm_fault_t snd_pcm_mmap_data_fault(struct vm_fault *vmf) +{ + struct snd_pcm_substream *substream = vmf->vma->vm_private_data; + struct snd_pcm_runtime *runtime; + unsigned long offset; + struct page * page; + size_t dma_bytes; + + if (substream == NULL) + return VM_FAULT_SIGBUS; + runtime = substream->runtime; + offset = vmf->pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if (offset > dma_bytes - PAGE_SIZE) + return VM_FAULT_SIGBUS; + if (substream->ops->page) + page = substream->ops->page(substream, offset); + else if (!snd_pcm_get_dma_buf(substream)) + page = virt_to_page(runtime->dma_area + offset); + else + page = snd_sgbuf_get_page(snd_pcm_get_dma_buf(substream), offset); + if (!page) + return VM_FAULT_SIGBUS; + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct snd_pcm_vm_ops_data = { + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, +}; + +static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = { + .open = snd_pcm_mmap_data_open, + .close = snd_pcm_mmap_data_close, + .fault = snd_pcm_mmap_data_fault, +}; + +/* + * mmap the DMA buffer on RAM + */ + +/** + * snd_pcm_lib_default_mmap - Default PCM data mmap function + * @substream: PCM substream + * @area: VMA + * + * This is the default mmap handler for PCM data. When mmap pcm_ops is NULL, + * this function is invoked implicitly. + * + * Return: zero if successful, or a negative error code + */ +int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP); + if (!substream->ops->page && + !snd_dma_buffer_mmap(snd_pcm_get_dma_buf(substream), area)) + return 0; + /* mmap with fault handler */ + area->vm_ops = &snd_pcm_vm_ops_data_fault; + return 0; +} +EXPORT_SYMBOL_GPL(snd_pcm_lib_default_mmap); + +/* + * mmap the DMA buffer on I/O memory area + */ +#if SNDRV_PCM_INFO_MMAP_IOMEM +/** + * snd_pcm_lib_mmap_iomem - Default PCM data mmap function for I/O mem + * @substream: PCM substream + * @area: VMA + * + * When your hardware uses the iomapped pages as the hardware buffer and + * wants to mmap it, pass this function as mmap pcm_ops. Note that this + * is supposed to work only on limited architectures. + * + * Return: zero if successful, or a negative error code + */ +int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream, + struct vm_area_struct *area) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + area->vm_page_prot = pgprot_noncached(area->vm_page_prot); + return vm_iomap_memory(area, runtime->dma_addr, runtime->dma_bytes); +} +EXPORT_SYMBOL(snd_pcm_lib_mmap_iomem); +#endif /* SNDRV_PCM_INFO_MMAP */ + +/* + * mmap DMA buffer + */ +int snd_pcm_mmap_data(struct snd_pcm_substream *substream, struct file *file, + struct vm_area_struct *area) +{ + struct snd_pcm_runtime *runtime; + long size; + unsigned long offset; + size_t dma_bytes; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (!(area->vm_flags & (VM_WRITE|VM_READ))) + return -EINVAL; + } else { + if (!(area->vm_flags & VM_READ)) + return -EINVAL; + } + runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_OPEN) + return -EBADFD; + if (!(runtime->info & SNDRV_PCM_INFO_MMAP)) + return -ENXIO; + if (runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) + return -EINVAL; + size = area->vm_end - area->vm_start; + offset = area->vm_pgoff << PAGE_SHIFT; + dma_bytes = PAGE_ALIGN(runtime->dma_bytes); + if ((size_t)size > dma_bytes) + return -EINVAL; + if (offset > dma_bytes - size) + return -EINVAL; + + area->vm_ops = &snd_pcm_vm_ops_data; + area->vm_private_data = substream; + if (substream->ops->mmap) + err = substream->ops->mmap(substream, area); + else + err = snd_pcm_lib_default_mmap(substream, area); + if (!err) + atomic_inc(&substream->mmap_count); + return err; +} +EXPORT_SYMBOL(snd_pcm_mmap_data); + +static int snd_pcm_mmap(struct file *file, struct vm_area_struct *area) +{ + struct snd_pcm_file * pcm_file; + struct snd_pcm_substream *substream; + unsigned long offset; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + + offset = area->vm_pgoff << PAGE_SHIFT; + switch (offset) { + case SNDRV_PCM_MMAP_OFFSET_STATUS_OLD: + if (pcm_file->no_compat_mmap || !IS_ENABLED(CONFIG_64BIT)) + return -ENXIO; + fallthrough; + case SNDRV_PCM_MMAP_OFFSET_STATUS_NEW: + if (!pcm_status_mmap_allowed(pcm_file)) + return -ENXIO; + return snd_pcm_mmap_status(substream, file, area); + case SNDRV_PCM_MMAP_OFFSET_CONTROL_OLD: + if (pcm_file->no_compat_mmap || !IS_ENABLED(CONFIG_64BIT)) + return -ENXIO; + fallthrough; + case SNDRV_PCM_MMAP_OFFSET_CONTROL_NEW: + if (!pcm_control_mmap_allowed(pcm_file)) + return -ENXIO; + return snd_pcm_mmap_control(substream, file, area); + default: + return snd_pcm_mmap_data(substream, file, area); + } + return 0; +} + +static int snd_pcm_fasync(int fd, struct file * file, int on) +{ + struct snd_pcm_file * pcm_file; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + + pcm_file = file->private_data; + substream = pcm_file->substream; + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + if (runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; + return snd_fasync_helper(fd, file, on, &runtime->fasync); +} + +/* + * ioctl32 compat + */ +#ifdef CONFIG_COMPAT +#include "pcm_compat.c" +#else +#define snd_pcm_ioctl_compat NULL +#endif + +/* + * To be removed helpers to keep binary compatibility + */ + +#ifdef CONFIG_SND_SUPPORT_OLD_API +#define __OLD_TO_NEW_MASK(x) ((x&7)|((x&0x07fffff8)<<5)) +#define __NEW_TO_OLD_MASK(x) ((x&7)|((x&0xffffff00)>>5)) + +static void snd_pcm_hw_convert_from_old_params(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_params_old *oparams) +{ + unsigned int i; + + memset(params, 0, sizeof(*params)); + params->flags = oparams->flags; + for (i = 0; i < ARRAY_SIZE(oparams->masks); i++) + params->masks[i].bits[0] = oparams->masks[i]; + memcpy(params->intervals, oparams->intervals, sizeof(oparams->intervals)); + params->rmask = __OLD_TO_NEW_MASK(oparams->rmask); + params->cmask = __OLD_TO_NEW_MASK(oparams->cmask); + params->info = oparams->info; + params->msbits = oparams->msbits; + params->rate_num = oparams->rate_num; + params->rate_den = oparams->rate_den; + params->fifo_size = oparams->fifo_size; +} + +static void snd_pcm_hw_convert_to_old_params(struct snd_pcm_hw_params_old *oparams, + struct snd_pcm_hw_params *params) +{ + unsigned int i; + + memset(oparams, 0, sizeof(*oparams)); + oparams->flags = params->flags; + for (i = 0; i < ARRAY_SIZE(oparams->masks); i++) + oparams->masks[i] = params->masks[i].bits[0]; + memcpy(oparams->intervals, params->intervals, sizeof(oparams->intervals)); + oparams->rmask = __NEW_TO_OLD_MASK(params->rmask); + oparams->cmask = __NEW_TO_OLD_MASK(params->cmask); + oparams->info = params->info; + oparams->msbits = params->msbits; + oparams->rate_num = params->rate_num; + oparams->rate_den = params->rate_den; + oparams->fifo_size = params->fifo_size; +} + +static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams) +{ + struct snd_pcm_hw_params *params; + struct snd_pcm_hw_params_old *oparams = NULL; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + oparams = memdup_user(_oparams, sizeof(*oparams)); + if (IS_ERR(oparams)) { + err = PTR_ERR(oparams); + goto out; + } + snd_pcm_hw_convert_from_old_params(params, oparams); + err = snd_pcm_hw_refine(substream, params); + if (err < 0) + goto out_old; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + goto out_old; + + snd_pcm_hw_convert_to_old_params(oparams, params); + if (copy_to_user(_oparams, oparams, sizeof(*oparams))) + err = -EFAULT; +out_old: + kfree(oparams); +out: + kfree(params); + return err; +} + +static int snd_pcm_hw_params_old_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params_old __user * _oparams) +{ + struct snd_pcm_hw_params *params; + struct snd_pcm_hw_params_old *oparams = NULL; + int err; + + params = kmalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + oparams = memdup_user(_oparams, sizeof(*oparams)); + if (IS_ERR(oparams)) { + err = PTR_ERR(oparams); + goto out; + } + + snd_pcm_hw_convert_from_old_params(params, oparams); + err = snd_pcm_hw_params(substream, params); + if (err < 0) + goto out_old; + + snd_pcm_hw_convert_to_old_params(oparams, params); + if (copy_to_user(_oparams, oparams, sizeof(*oparams))) + err = -EFAULT; +out_old: + kfree(oparams); +out: + kfree(params); + return err; +} +#endif /* CONFIG_SND_SUPPORT_OLD_API */ + +#ifndef CONFIG_MMU +static unsigned long snd_pcm_get_unmapped_area(struct file *file, + unsigned long addr, + unsigned long len, + unsigned long pgoff, + unsigned long flags) +{ + struct snd_pcm_file *pcm_file = file->private_data; + struct snd_pcm_substream *substream = pcm_file->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long offset = pgoff << PAGE_SHIFT; + + switch (offset) { + case SNDRV_PCM_MMAP_OFFSET_STATUS_NEW: + return (unsigned long)runtime->status; + case SNDRV_PCM_MMAP_OFFSET_CONTROL_NEW: + return (unsigned long)runtime->control; + default: + return (unsigned long)runtime->dma_area + offset; + } +} +#else +# define snd_pcm_get_unmapped_area NULL +#endif + +/* + * Register section + */ + +const struct file_operations snd_pcm_f_ops[2] = { + { + .owner = THIS_MODULE, + .write = snd_pcm_write, + .write_iter = snd_pcm_writev, + .open = snd_pcm_playback_open, + .release = snd_pcm_release, + .llseek = no_llseek, + .poll = snd_pcm_poll, + .unlocked_ioctl = snd_pcm_ioctl, + .compat_ioctl = snd_pcm_ioctl_compat, + .mmap = snd_pcm_mmap, + .fasync = snd_pcm_fasync, + .get_unmapped_area = snd_pcm_get_unmapped_area, + }, + { + .owner = THIS_MODULE, + .read = snd_pcm_read, + .read_iter = snd_pcm_readv, + .open = snd_pcm_capture_open, + .release = snd_pcm_release, + .llseek = no_llseek, + .poll = snd_pcm_poll, + .unlocked_ioctl = snd_pcm_ioctl, + .compat_ioctl = snd_pcm_ioctl_compat, + .mmap = snd_pcm_mmap, + .fasync = snd_pcm_fasync, + .get_unmapped_area = snd_pcm_get_unmapped_area, + } +}; diff --git a/sound/core/pcm_param_trace.h b/sound/core/pcm_param_trace.h new file mode 100644 index 0000000000..08abba3133 --- /dev/null +++ b/sound/core/pcm_param_trace.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_pcm + +#if !defined(_PCM_PARAMS_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _PCM_PARAMS_TRACE_H + +#include <linux/tracepoint.h> + +#define HW_PARAM_ENTRY(param) {SNDRV_PCM_HW_PARAM_##param, #param} +#define hw_param_labels \ + HW_PARAM_ENTRY(ACCESS), \ + HW_PARAM_ENTRY(FORMAT), \ + HW_PARAM_ENTRY(SUBFORMAT), \ + HW_PARAM_ENTRY(SAMPLE_BITS), \ + HW_PARAM_ENTRY(FRAME_BITS), \ + HW_PARAM_ENTRY(CHANNELS), \ + HW_PARAM_ENTRY(RATE), \ + HW_PARAM_ENTRY(PERIOD_TIME), \ + HW_PARAM_ENTRY(PERIOD_SIZE), \ + HW_PARAM_ENTRY(PERIOD_BYTES), \ + HW_PARAM_ENTRY(PERIODS), \ + HW_PARAM_ENTRY(BUFFER_TIME), \ + HW_PARAM_ENTRY(BUFFER_SIZE), \ + HW_PARAM_ENTRY(BUFFER_BYTES), \ + HW_PARAM_ENTRY(TICK_TIME) + +TRACE_EVENT(hw_mask_param, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_hw_param_t type, int index, const struct snd_mask *prev, const struct snd_mask *curr), + TP_ARGS(substream, type, index, prev, curr), + TP_STRUCT__entry( + __field(int, card) + __field(int, device) + __field(int, subdevice) + __field(int, direction) + __field(snd_pcm_hw_param_t, type) + __field(int, index) + __field(int, total) + __array(__u32, prev_bits, 8) + __array(__u32, curr_bits, 8) + ), + TP_fast_assign( + __entry->card = substream->pcm->card->number; + __entry->device = substream->pcm->device; + __entry->subdevice = substream->number; + __entry->direction = substream->stream; + __entry->type = type; + __entry->index = index; + __entry->total = substream->runtime->hw_constraints.rules_num; + memcpy(__entry->prev_bits, prev->bits, sizeof(__u32) * 8); + memcpy(__entry->curr_bits, curr->bits, sizeof(__u32) * 8); + ), + TP_printk("pcmC%dD%d%s:%d %03d/%03d %s %08x%08x%08x%08x %08x%08x%08x%08x", + __entry->card, + __entry->device, + __entry->direction ? "c" : "p", + __entry->subdevice, + __entry->index, + __entry->total, + __print_symbolic(__entry->type, hw_param_labels), + __entry->prev_bits[3], __entry->prev_bits[2], + __entry->prev_bits[1], __entry->prev_bits[0], + __entry->curr_bits[3], __entry->curr_bits[2], + __entry->curr_bits[1], __entry->curr_bits[0] + ) +); + +TRACE_EVENT(hw_interval_param, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_hw_param_t type, int index, const struct snd_interval *prev, const struct snd_interval *curr), + TP_ARGS(substream, type, index, prev, curr), + TP_STRUCT__entry( + __field(int, card) + __field(int, device) + __field(int, subdevice) + __field(int, direction) + __field(snd_pcm_hw_param_t, type) + __field(int, index) + __field(int, total) + __field(unsigned int, prev_min) + __field(unsigned int, prev_max) + __field(unsigned int, prev_openmin) + __field(unsigned int, prev_openmax) + __field(unsigned int, prev_integer) + __field(unsigned int, prev_empty) + __field(unsigned int, curr_min) + __field(unsigned int, curr_max) + __field(unsigned int, curr_openmin) + __field(unsigned int, curr_openmax) + __field(unsigned int, curr_integer) + __field(unsigned int, curr_empty) + ), + TP_fast_assign( + __entry->card = substream->pcm->card->number; + __entry->device = substream->pcm->device; + __entry->subdevice = substream->number; + __entry->direction = substream->stream; + __entry->type = type; + __entry->index = index; + __entry->total = substream->runtime->hw_constraints.rules_num; + __entry->prev_min = prev->min; + __entry->prev_max = prev->max; + __entry->prev_openmin = prev->openmin; + __entry->prev_openmax = prev->openmax; + __entry->prev_integer = prev->integer; + __entry->prev_empty = prev->empty; + __entry->curr_min = curr->min; + __entry->curr_max = curr->max; + __entry->curr_openmin = curr->openmin; + __entry->curr_openmax = curr->openmax; + __entry->curr_integer = curr->integer; + __entry->curr_empty = curr->empty; + ), + TP_printk("pcmC%dD%d%s:%d %03d/%03d %s %d %d %s%u %u%s %d %d %s%u %u%s", + __entry->card, + __entry->device, + __entry->direction ? "c" : "p", + __entry->subdevice, + __entry->index, + __entry->total, + __print_symbolic(__entry->type, hw_param_labels), + __entry->prev_empty, + __entry->prev_integer, + __entry->prev_openmin ? "(" : "[", + __entry->prev_min, + __entry->prev_max, + __entry->prev_openmax ? ")" : "]", + __entry->curr_empty, + __entry->curr_integer, + __entry->curr_openmin ? "(" : "[", + __entry->curr_min, + __entry->curr_max, + __entry->curr_openmax ? ")" : "]" + ) +); + +#endif /* _PCM_PARAMS_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE pcm_param_trace +#include <trace/define_trace.h> diff --git a/sound/core/pcm_timer.c b/sound/core/pcm_timer.c new file mode 100644 index 0000000000..c43484b22b --- /dev/null +++ b/sound/core/pcm_timer.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Digital Audio (PCM) abstract layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/time.h> +#include <linux/gcd.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/timer.h> + +#include "pcm_local.h" + +/* + * Timer functions + */ + +void snd_pcm_timer_resolution_change(struct snd_pcm_substream *substream) +{ + unsigned long rate, mult, fsize, l, post; + struct snd_pcm_runtime *runtime = substream->runtime; + + mult = 1000000000; + rate = runtime->rate; + if (snd_BUG_ON(!rate)) + return; + l = gcd(mult, rate); + mult /= l; + rate /= l; + fsize = runtime->period_size; + if (snd_BUG_ON(!fsize)) + return; + l = gcd(rate, fsize); + rate /= l; + fsize /= l; + post = 1; + while ((mult * fsize) / fsize != mult) { + mult /= 2; + post *= 2; + } + if (rate == 0) { + pcm_err(substream->pcm, + "pcm timer resolution out of range (rate = %u, period_size = %lu)\n", + runtime->rate, runtime->period_size); + runtime->timer_resolution = -1; + return; + } + runtime->timer_resolution = (mult * fsize / rate) * post; +} + +static unsigned long snd_pcm_timer_resolution(struct snd_timer * timer) +{ + struct snd_pcm_substream *substream; + + substream = timer->private_data; + return substream->runtime ? substream->runtime->timer_resolution : 0; +} + +static int snd_pcm_timer_start(struct snd_timer * timer) +{ + struct snd_pcm_substream *substream; + + substream = snd_timer_chip(timer); + substream->timer_running = 1; + return 0; +} + +static int snd_pcm_timer_stop(struct snd_timer * timer) +{ + struct snd_pcm_substream *substream; + + substream = snd_timer_chip(timer); + substream->timer_running = 0; + return 0; +} + +static const struct snd_timer_hardware snd_pcm_timer = +{ + .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_SLAVE, + .resolution = 0, + .ticks = 1, + .c_resolution = snd_pcm_timer_resolution, + .start = snd_pcm_timer_start, + .stop = snd_pcm_timer_stop, +}; + +/* + * Init functions + */ + +static void snd_pcm_timer_free(struct snd_timer *timer) +{ + struct snd_pcm_substream *substream = timer->private_data; + substream->timer = NULL; +} + +void snd_pcm_timer_init(struct snd_pcm_substream *substream) +{ + struct snd_timer_id tid; + struct snd_timer *timer; + + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.dev_class = SNDRV_TIMER_CLASS_PCM; + tid.card = substream->pcm->card->number; + tid.device = substream->pcm->device; + tid.subdevice = (substream->number << 1) | (substream->stream & 1); + if (snd_timer_new(substream->pcm->card, "PCM", &tid, &timer) < 0) + return; + sprintf(timer->name, "PCM %s %i-%i-%i", + substream->stream == SNDRV_PCM_STREAM_CAPTURE ? + "capture" : "playback", + tid.card, tid.device, tid.subdevice); + timer->hw = snd_pcm_timer; + if (snd_device_register(timer->card, timer) < 0) { + snd_device_free(timer->card, timer); + return; + } + timer->private_data = substream; + timer->private_free = snd_pcm_timer_free; + substream->timer = timer; +} + +void snd_pcm_timer_done(struct snd_pcm_substream *substream) +{ + if (substream->timer) { + snd_device_free(substream->pcm->card, substream->timer); + substream->timer = NULL; + } +} diff --git a/sound/core/pcm_trace.h b/sound/core/pcm_trace.h new file mode 100644 index 0000000000..350b40b906 --- /dev/null +++ b/sound/core/pcm_trace.h @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_pcm +#define TRACE_INCLUDE_FILE pcm_trace + +#if !defined(_PCM_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _PCM_TRACE_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(hwptr, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_uframes_t pos, bool irq), + TP_ARGS(substream, pos, irq), + TP_STRUCT__entry( + __field( bool, in_interrupt ) + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __field( snd_pcm_uframes_t, pos ) + __field( snd_pcm_uframes_t, period_size ) + __field( snd_pcm_uframes_t, buffer_size ) + __field( snd_pcm_uframes_t, old_hw_ptr ) + __field( snd_pcm_uframes_t, hw_ptr_base ) + ), + TP_fast_assign( + __entry->in_interrupt = (irq); + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __entry->pos = (pos); + __entry->period_size = (substream)->runtime->period_size; + __entry->buffer_size = (substream)->runtime->buffer_size; + __entry->old_hw_ptr = (substream)->runtime->status->hw_ptr; + __entry->hw_ptr_base = (substream)->runtime->hw_ptr_base; + ), + TP_printk("pcmC%dD%d%s/sub%d: %s: pos=%lu, old=%lu, base=%lu, period=%lu, buf=%lu", + __entry->card, __entry->device, + __entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? "p" : "c", + __entry->number, + __entry->in_interrupt ? "IRQ" : "POS", + (unsigned long)__entry->pos, + (unsigned long)__entry->old_hw_ptr, + (unsigned long)__entry->hw_ptr_base, + (unsigned long)__entry->period_size, + (unsigned long)__entry->buffer_size) +); + +TRACE_EVENT(xrun, + TP_PROTO(struct snd_pcm_substream *substream), + TP_ARGS(substream), + TP_STRUCT__entry( + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __field( snd_pcm_uframes_t, period_size ) + __field( snd_pcm_uframes_t, buffer_size ) + __field( snd_pcm_uframes_t, old_hw_ptr ) + __field( snd_pcm_uframes_t, hw_ptr_base ) + ), + TP_fast_assign( + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __entry->period_size = (substream)->runtime->period_size; + __entry->buffer_size = (substream)->runtime->buffer_size; + __entry->old_hw_ptr = (substream)->runtime->status->hw_ptr; + __entry->hw_ptr_base = (substream)->runtime->hw_ptr_base; + ), + TP_printk("pcmC%dD%d%s/sub%d: XRUN: old=%lu, base=%lu, period=%lu, buf=%lu", + __entry->card, __entry->device, + __entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? "p" : "c", + __entry->number, + (unsigned long)__entry->old_hw_ptr, + (unsigned long)__entry->hw_ptr_base, + (unsigned long)__entry->period_size, + (unsigned long)__entry->buffer_size) +); + +TRACE_EVENT(hw_ptr_error, + TP_PROTO(struct snd_pcm_substream *substream, const char *why), + TP_ARGS(substream, why), + TP_STRUCT__entry( + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __string( reason, why ) + ), + TP_fast_assign( + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __assign_str(reason, why); + ), + TP_printk("pcmC%dD%d%s/sub%d: ERROR: %s", + __entry->card, __entry->device, + __entry->stream == SNDRV_PCM_STREAM_PLAYBACK ? "p" : "c", + __entry->number, __get_str(reason)) +); + +TRACE_EVENT(applptr, + TP_PROTO(struct snd_pcm_substream *substream, snd_pcm_uframes_t prev, snd_pcm_uframes_t curr), + TP_ARGS(substream, prev, curr), + TP_STRUCT__entry( + __field( unsigned int, card ) + __field( unsigned int, device ) + __field( unsigned int, number ) + __field( unsigned int, stream ) + __field( snd_pcm_uframes_t, prev ) + __field( snd_pcm_uframes_t, curr ) + __field( snd_pcm_uframes_t, avail ) + __field( snd_pcm_uframes_t, period_size ) + __field( snd_pcm_uframes_t, buffer_size ) + ), + TP_fast_assign( + __entry->card = (substream)->pcm->card->number; + __entry->device = (substream)->pcm->device; + __entry->number = (substream)->number; + __entry->stream = (substream)->stream; + __entry->prev = (prev); + __entry->curr = (curr); + __entry->avail = (substream)->stream ? snd_pcm_capture_avail(substream->runtime) : snd_pcm_playback_avail(substream->runtime); + __entry->period_size = (substream)->runtime->period_size; + __entry->buffer_size = (substream)->runtime->buffer_size; + ), + TP_printk("pcmC%dD%d%s/sub%d: prev=%lu, curr=%lu, avail=%lu, period=%lu, buf=%lu", + __entry->card, + __entry->device, + __entry->stream ? "c" : "p", + __entry->number, + __entry->prev, + __entry->curr, + __entry->avail, + __entry->period_size, + __entry->buffer_size + ) +); + +#endif /* _PCM_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include <trace/define_trace.h> diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c new file mode 100644 index 0000000000..1431cb9978 --- /dev/null +++ b/sound/core/rawmidi.c @@ -0,0 +1,2202 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Abstract layer for MIDI v1.0 stream + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <sound/core.h> +#include <linux/major.h> +#include <linux/init.h> +#include <linux/sched/signal.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mm.h> +#include <linux/nospec.h> +#include <sound/rawmidi.h> +#include <sound/info.h> +#include <sound/control.h> +#include <sound/minors.h> +#include <sound/initval.h> +#include <sound/ump.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA."); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_SND_OSSEMUL +static int midi_map[SNDRV_CARDS]; +static int amidi_map[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 1}; +module_param_array(midi_map, int, NULL, 0444); +MODULE_PARM_DESC(midi_map, "Raw MIDI device number assigned to 1st OSS device."); +module_param_array(amidi_map, int, NULL, 0444); +MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); +#endif /* CONFIG_SND_OSSEMUL */ + +static int snd_rawmidi_dev_free(struct snd_device *device); +static int snd_rawmidi_dev_register(struct snd_device *device); +static int snd_rawmidi_dev_disconnect(struct snd_device *device); + +static LIST_HEAD(snd_rawmidi_devices); +static DEFINE_MUTEX(register_mutex); + +#define rmidi_err(rmidi, fmt, args...) \ + dev_err((rmidi)->dev, fmt, ##args) +#define rmidi_warn(rmidi, fmt, args...) \ + dev_warn((rmidi)->dev, fmt, ##args) +#define rmidi_dbg(rmidi, fmt, args...) \ + dev_dbg((rmidi)->dev, fmt, ##args) + +struct snd_rawmidi_status32 { + s32 stream; + s32 tstamp_sec; /* Timestamp */ + s32 tstamp_nsec; + u32 avail; /* available bytes */ + u32 xruns; /* count of overruns since last status (in bytes) */ + unsigned char reserved[16]; /* reserved for future use */ +}; + +#define SNDRV_RAWMIDI_IOCTL_STATUS32 _IOWR('W', 0x20, struct snd_rawmidi_status32) + +struct snd_rawmidi_status64 { + int stream; + u8 rsvd[4]; /* alignment */ + s64 tstamp_sec; /* Timestamp */ + s64 tstamp_nsec; + size_t avail; /* available bytes */ + size_t xruns; /* count of overruns since last status (in bytes) */ + unsigned char reserved[16]; /* reserved for future use */ +}; + +#define SNDRV_RAWMIDI_IOCTL_STATUS64 _IOWR('W', 0x20, struct snd_rawmidi_status64) + +#define rawmidi_is_ump(rmidi) \ + (IS_ENABLED(CONFIG_SND_UMP) && ((rmidi)->info_flags & SNDRV_RAWMIDI_INFO_UMP)) + +static struct snd_rawmidi *snd_rawmidi_search(struct snd_card *card, int device) +{ + struct snd_rawmidi *rawmidi; + + list_for_each_entry(rawmidi, &snd_rawmidi_devices, list) + if (rawmidi->card == card && rawmidi->device == device) + return rawmidi; + return NULL; +} + +static inline unsigned short snd_rawmidi_file_flags(struct file *file) +{ + switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) { + case FMODE_WRITE: + return SNDRV_RAWMIDI_LFLG_OUTPUT; + case FMODE_READ: + return SNDRV_RAWMIDI_LFLG_INPUT; + default: + return SNDRV_RAWMIDI_LFLG_OPEN; + } +} + +static inline bool __snd_rawmidi_ready(struct snd_rawmidi_runtime *runtime) +{ + return runtime->avail >= runtime->avail_min; +} + +static bool snd_rawmidi_ready(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + bool ready; + + spin_lock_irqsave(&substream->lock, flags); + ready = __snd_rawmidi_ready(substream->runtime); + spin_unlock_irqrestore(&substream->lock, flags); + return ready; +} + +static inline int snd_rawmidi_ready_append(struct snd_rawmidi_substream *substream, + size_t count) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + return runtime->avail >= runtime->avail_min && + (!substream->append || runtime->avail >= count); +} + +static void snd_rawmidi_input_event_work(struct work_struct *work) +{ + struct snd_rawmidi_runtime *runtime = + container_of(work, struct snd_rawmidi_runtime, event_work); + + if (runtime->event) + runtime->event(runtime->substream); +} + +/* buffer refcount management: call with substream->lock held */ +static inline void snd_rawmidi_buffer_ref(struct snd_rawmidi_runtime *runtime) +{ + runtime->buffer_ref++; +} + +static inline void snd_rawmidi_buffer_unref(struct snd_rawmidi_runtime *runtime) +{ + runtime->buffer_ref--; +} + +static void snd_rawmidi_buffer_ref_sync(struct snd_rawmidi_substream *substream) +{ + int loop = HZ; + + spin_lock_irq(&substream->lock); + while (substream->runtime->buffer_ref) { + spin_unlock_irq(&substream->lock); + if (!--loop) { + rmidi_err(substream->rmidi, "Buffer ref sync timeout\n"); + return; + } + schedule_timeout_uninterruptible(1); + spin_lock_irq(&substream->lock); + } + spin_unlock_irq(&substream->lock); +} + +static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime; + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + if (!runtime) + return -ENOMEM; + runtime->substream = substream; + init_waitqueue_head(&runtime->sleep); + INIT_WORK(&runtime->event_work, snd_rawmidi_input_event_work); + runtime->event = NULL; + runtime->buffer_size = PAGE_SIZE; + runtime->avail_min = 1; + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + runtime->avail = 0; + else + runtime->avail = runtime->buffer_size; + runtime->buffer = kvzalloc(runtime->buffer_size, GFP_KERNEL); + if (!runtime->buffer) { + kfree(runtime); + return -ENOMEM; + } + runtime->appl_ptr = runtime->hw_ptr = 0; + substream->runtime = runtime; + if (rawmidi_is_ump(substream->rmidi)) + runtime->align = 3; + return 0; +} + +/* get the current alignment (either 0 or 3) */ +static inline int get_align(struct snd_rawmidi_runtime *runtime) +{ + if (IS_ENABLED(CONFIG_SND_UMP)) + return runtime->align; + else + return 0; +} + +/* get the trimmed size with the current alignment */ +#define get_aligned_size(runtime, size) ((size) & ~get_align(runtime)) + +static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + kvfree(runtime->buffer); + kfree(runtime); + substream->runtime = NULL; + return 0; +} + +static inline void snd_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (!substream->opened) + return; + substream->ops->trigger(substream, up); +} + +static void snd_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + if (!substream->opened) + return; + substream->ops->trigger(substream, up); + if (!up) + cancel_work_sync(&substream->runtime->event_work); +} + +static void __reset_runtime_ptrs(struct snd_rawmidi_runtime *runtime, + bool is_input) +{ + runtime->drain = 0; + runtime->appl_ptr = runtime->hw_ptr = 0; + runtime->avail = is_input ? 0 : runtime->buffer_size; +} + +static void reset_runtime_ptrs(struct snd_rawmidi_substream *substream, + bool is_input) +{ + unsigned long flags; + + spin_lock_irqsave(&substream->lock, flags); + if (substream->opened && substream->runtime) + __reset_runtime_ptrs(substream->runtime, is_input); + spin_unlock_irqrestore(&substream->lock, flags); +} + +int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream) +{ + snd_rawmidi_output_trigger(substream, 0); + reset_runtime_ptrs(substream, false); + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_drop_output); + +int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) +{ + int err = 0; + long timeout; + struct snd_rawmidi_runtime *runtime; + + spin_lock_irq(&substream->lock); + runtime = substream->runtime; + if (!substream->opened || !runtime || !runtime->buffer) { + err = -EINVAL; + } else { + snd_rawmidi_buffer_ref(runtime); + runtime->drain = 1; + } + spin_unlock_irq(&substream->lock); + if (err < 0) + return err; + + timeout = wait_event_interruptible_timeout(runtime->sleep, + (runtime->avail >= runtime->buffer_size), + 10*HZ); + + spin_lock_irq(&substream->lock); + if (signal_pending(current)) + err = -ERESTARTSYS; + if (runtime->avail < runtime->buffer_size && !timeout) { + rmidi_warn(substream->rmidi, + "rawmidi drain error (avail = %li, buffer_size = %li)\n", + (long)runtime->avail, (long)runtime->buffer_size); + err = -EIO; + } + runtime->drain = 0; + spin_unlock_irq(&substream->lock); + + if (err != -ERESTARTSYS) { + /* we need wait a while to make sure that Tx FIFOs are empty */ + if (substream->ops->drain) + substream->ops->drain(substream); + else + msleep(50); + snd_rawmidi_drop_output(substream); + } + + spin_lock_irq(&substream->lock); + snd_rawmidi_buffer_unref(runtime); + spin_unlock_irq(&substream->lock); + + return err; +} +EXPORT_SYMBOL(snd_rawmidi_drain_output); + +int snd_rawmidi_drain_input(struct snd_rawmidi_substream *substream) +{ + snd_rawmidi_input_trigger(substream, 0); + reset_runtime_ptrs(substream, true); + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_drain_input); + +/* look for an available substream for the given stream direction; + * if a specific subdevice is given, try to assign it + */ +static int assign_substream(struct snd_rawmidi *rmidi, int subdevice, + int stream, int mode, + struct snd_rawmidi_substream **sub_ret) +{ + struct snd_rawmidi_substream *substream; + struct snd_rawmidi_str *s = &rmidi->streams[stream]; + static const unsigned int info_flags[2] = { + [SNDRV_RAWMIDI_STREAM_OUTPUT] = SNDRV_RAWMIDI_INFO_OUTPUT, + [SNDRV_RAWMIDI_STREAM_INPUT] = SNDRV_RAWMIDI_INFO_INPUT, + }; + + if (!(rmidi->info_flags & info_flags[stream])) + return -ENXIO; + if (subdevice >= 0 && subdevice >= s->substream_count) + return -ENODEV; + + list_for_each_entry(substream, &s->substreams, list) { + if (substream->opened) { + if (stream == SNDRV_RAWMIDI_STREAM_INPUT || + !(mode & SNDRV_RAWMIDI_LFLG_APPEND) || + !substream->append) + continue; + } + if (subdevice < 0 || subdevice == substream->number) { + *sub_ret = substream; + return 0; + } + } + return -EAGAIN; +} + +/* open and do ref-counting for the given substream */ +static int open_substream(struct snd_rawmidi *rmidi, + struct snd_rawmidi_substream *substream, + int mode) +{ + int err; + + if (substream->use_count == 0) { + err = snd_rawmidi_runtime_create(substream); + if (err < 0) + return err; + err = substream->ops->open(substream); + if (err < 0) { + snd_rawmidi_runtime_free(substream); + return err; + } + spin_lock_irq(&substream->lock); + substream->opened = 1; + substream->active_sensing = 0; + if (mode & SNDRV_RAWMIDI_LFLG_APPEND) + substream->append = 1; + substream->pid = get_pid(task_pid(current)); + rmidi->streams[substream->stream].substream_opened++; + spin_unlock_irq(&substream->lock); + } + substream->use_count++; + return 0; +} + +static void close_substream(struct snd_rawmidi *rmidi, + struct snd_rawmidi_substream *substream, + int cleanup); + +static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, + struct snd_rawmidi_file *rfile) +{ + struct snd_rawmidi_substream *sinput = NULL, *soutput = NULL; + int err; + + rfile->input = rfile->output = NULL; + if (mode & SNDRV_RAWMIDI_LFLG_INPUT) { + err = assign_substream(rmidi, subdevice, + SNDRV_RAWMIDI_STREAM_INPUT, + mode, &sinput); + if (err < 0) + return err; + } + if (mode & SNDRV_RAWMIDI_LFLG_OUTPUT) { + err = assign_substream(rmidi, subdevice, + SNDRV_RAWMIDI_STREAM_OUTPUT, + mode, &soutput); + if (err < 0) + return err; + } + + if (sinput) { + err = open_substream(rmidi, sinput, mode); + if (err < 0) + return err; + } + if (soutput) { + err = open_substream(rmidi, soutput, mode); + if (err < 0) { + if (sinput) + close_substream(rmidi, sinput, 0); + return err; + } + } + + rfile->rmidi = rmidi; + rfile->input = sinput; + rfile->output = soutput; + return 0; +} + +/* called from sound/core/seq/seq_midi.c */ +int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, + int mode, struct snd_rawmidi_file *rfile) +{ + int err; + + if (snd_BUG_ON(!rfile)) + return -EINVAL; + if (!try_module_get(rmidi->card->module)) + return -ENXIO; + + mutex_lock(&rmidi->open_mutex); + err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); + mutex_unlock(&rmidi->open_mutex); + if (err < 0) + module_put(rmidi->card->module); + return err; +} +EXPORT_SYMBOL(snd_rawmidi_kernel_open); + +static int snd_rawmidi_open(struct inode *inode, struct file *file) +{ + int maj = imajor(inode); + struct snd_card *card; + int subdevice; + unsigned short fflags; + int err; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_file *rawmidi_file = NULL; + wait_queue_entry_t wait; + + if ((file->f_flags & O_APPEND) && !(file->f_flags & O_NONBLOCK)) + return -EINVAL; /* invalid combination */ + + err = stream_open(inode, file); + if (err < 0) + return err; + + if (maj == snd_major) { + rmidi = snd_lookup_minor_data(iminor(inode), + SNDRV_DEVICE_TYPE_RAWMIDI); +#ifdef CONFIG_SND_OSSEMUL + } else if (maj == SOUND_MAJOR) { + rmidi = snd_lookup_oss_minor_data(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_MIDI); +#endif + } else + return -ENXIO; + + if (rmidi == NULL) + return -ENODEV; + + if (!try_module_get(rmidi->card->module)) { + snd_card_unref(rmidi->card); + return -ENXIO; + } + + mutex_lock(&rmidi->open_mutex); + card = rmidi->card; + err = snd_card_file_add(card, file); + if (err < 0) + goto __error_card; + fflags = snd_rawmidi_file_flags(file); + if ((file->f_flags & O_APPEND) || maj == SOUND_MAJOR) /* OSS emul? */ + fflags |= SNDRV_RAWMIDI_LFLG_APPEND; + rawmidi_file = kmalloc(sizeof(*rawmidi_file), GFP_KERNEL); + if (rawmidi_file == NULL) { + err = -ENOMEM; + goto __error; + } + rawmidi_file->user_pversion = 0; + init_waitqueue_entry(&wait, current); + add_wait_queue(&rmidi->open_wait, &wait); + while (1) { + subdevice = snd_ctl_get_preferred_subdevice(card, SND_CTL_SUBDEV_RAWMIDI); + err = rawmidi_open_priv(rmidi, subdevice, fflags, rawmidi_file); + if (err >= 0) + break; + if (err == -EAGAIN) { + if (file->f_flags & O_NONBLOCK) { + err = -EBUSY; + break; + } + } else + break; + set_current_state(TASK_INTERRUPTIBLE); + mutex_unlock(&rmidi->open_mutex); + schedule(); + mutex_lock(&rmidi->open_mutex); + if (rmidi->card->shutdown) { + err = -ENODEV; + break; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + break; + } + } + remove_wait_queue(&rmidi->open_wait, &wait); + if (err < 0) { + kfree(rawmidi_file); + goto __error; + } +#ifdef CONFIG_SND_OSSEMUL + if (rawmidi_file->input && rawmidi_file->input->runtime) + rawmidi_file->input->runtime->oss = (maj == SOUND_MAJOR); + if (rawmidi_file->output && rawmidi_file->output->runtime) + rawmidi_file->output->runtime->oss = (maj == SOUND_MAJOR); +#endif + file->private_data = rawmidi_file; + mutex_unlock(&rmidi->open_mutex); + snd_card_unref(rmidi->card); + return 0; + + __error: + snd_card_file_remove(card, file); + __error_card: + mutex_unlock(&rmidi->open_mutex); + module_put(rmidi->card->module); + snd_card_unref(rmidi->card); + return err; +} + +static void close_substream(struct snd_rawmidi *rmidi, + struct snd_rawmidi_substream *substream, + int cleanup) +{ + if (--substream->use_count) + return; + + if (cleanup) { + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + snd_rawmidi_input_trigger(substream, 0); + else { + if (substream->active_sensing) { + unsigned char buf = 0xfe; + /* sending single active sensing message + * to shut the device up + */ + snd_rawmidi_kernel_write(substream, &buf, 1); + } + if (snd_rawmidi_drain_output(substream) == -ERESTARTSYS) + snd_rawmidi_output_trigger(substream, 0); + } + snd_rawmidi_buffer_ref_sync(substream); + } + spin_lock_irq(&substream->lock); + substream->opened = 0; + substream->append = 0; + spin_unlock_irq(&substream->lock); + substream->ops->close(substream); + if (substream->runtime->private_free) + substream->runtime->private_free(substream); + snd_rawmidi_runtime_free(substream); + put_pid(substream->pid); + substream->pid = NULL; + rmidi->streams[substream->stream].substream_opened--; +} + +static void rawmidi_release_priv(struct snd_rawmidi_file *rfile) +{ + struct snd_rawmidi *rmidi; + + rmidi = rfile->rmidi; + mutex_lock(&rmidi->open_mutex); + if (rfile->input) { + close_substream(rmidi, rfile->input, 1); + rfile->input = NULL; + } + if (rfile->output) { + close_substream(rmidi, rfile->output, 1); + rfile->output = NULL; + } + rfile->rmidi = NULL; + mutex_unlock(&rmidi->open_mutex); + wake_up(&rmidi->open_wait); +} + +/* called from sound/core/seq/seq_midi.c */ +int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile) +{ + struct snd_rawmidi *rmidi; + + if (snd_BUG_ON(!rfile)) + return -ENXIO; + + rmidi = rfile->rmidi; + rawmidi_release_priv(rfile); + module_put(rmidi->card->module); + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_kernel_release); + +static int snd_rawmidi_release(struct inode *inode, struct file *file) +{ + struct snd_rawmidi_file *rfile; + struct snd_rawmidi *rmidi; + struct module *module; + + rfile = file->private_data; + rmidi = rfile->rmidi; + rawmidi_release_priv(rfile); + kfree(rfile); + module = rmidi->card->module; + snd_card_file_remove(rmidi->card, file); + module_put(module); + return 0; +} + +static int snd_rawmidi_info(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_info *info) +{ + struct snd_rawmidi *rmidi; + + if (substream == NULL) + return -ENODEV; + rmidi = substream->rmidi; + memset(info, 0, sizeof(*info)); + info->card = rmidi->card->number; + info->device = rmidi->device; + info->subdevice = substream->number; + info->stream = substream->stream; + info->flags = rmidi->info_flags; + strcpy(info->id, rmidi->id); + strcpy(info->name, rmidi->name); + strcpy(info->subname, substream->name); + info->subdevices_count = substream->pstr->substream_count; + info->subdevices_avail = (substream->pstr->substream_count - + substream->pstr->substream_opened); + return 0; +} + +static int snd_rawmidi_info_user(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_info __user *_info) +{ + struct snd_rawmidi_info info; + int err; + + err = snd_rawmidi_info(substream, &info); + if (err < 0) + return err; + if (copy_to_user(_info, &info, sizeof(struct snd_rawmidi_info))) + return -EFAULT; + return 0; +} + +static int __snd_rawmidi_info_select(struct snd_card *card, + struct snd_rawmidi_info *info) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *pstr; + struct snd_rawmidi_substream *substream; + + rmidi = snd_rawmidi_search(card, info->device); + if (!rmidi) + return -ENXIO; + if (info->stream < 0 || info->stream > 1) + return -EINVAL; + info->stream = array_index_nospec(info->stream, 2); + pstr = &rmidi->streams[info->stream]; + if (pstr->substream_count == 0) + return -ENOENT; + if (info->subdevice >= pstr->substream_count) + return -ENXIO; + list_for_each_entry(substream, &pstr->substreams, list) { + if ((unsigned int)substream->number == info->subdevice) + return snd_rawmidi_info(substream, info); + } + return -ENXIO; +} + +int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info) +{ + int ret; + + mutex_lock(®ister_mutex); + ret = __snd_rawmidi_info_select(card, info); + mutex_unlock(®ister_mutex); + return ret; +} +EXPORT_SYMBOL(snd_rawmidi_info_select); + +static int snd_rawmidi_info_select_user(struct snd_card *card, + struct snd_rawmidi_info __user *_info) +{ + int err; + struct snd_rawmidi_info info; + + if (get_user(info.device, &_info->device)) + return -EFAULT; + if (get_user(info.stream, &_info->stream)) + return -EFAULT; + if (get_user(info.subdevice, &_info->subdevice)) + return -EFAULT; + err = snd_rawmidi_info_select(card, &info); + if (err < 0) + return err; + if (copy_to_user(_info, &info, sizeof(struct snd_rawmidi_info))) + return -EFAULT; + return 0; +} + +static int resize_runtime_buffer(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params, + bool is_input) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + char *newbuf, *oldbuf; + unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK; + + if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L) + return -EINVAL; + if (framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP && (params->buffer_size & 0x1f) != 0) + return -EINVAL; + if (params->avail_min < 1 || params->avail_min > params->buffer_size) + return -EINVAL; + if (params->buffer_size & get_align(runtime)) + return -EINVAL; + if (params->buffer_size != runtime->buffer_size) { + newbuf = kvzalloc(params->buffer_size, GFP_KERNEL); + if (!newbuf) + return -ENOMEM; + spin_lock_irq(&substream->lock); + if (runtime->buffer_ref) { + spin_unlock_irq(&substream->lock); + kvfree(newbuf); + return -EBUSY; + } + oldbuf = runtime->buffer; + runtime->buffer = newbuf; + runtime->buffer_size = params->buffer_size; + __reset_runtime_ptrs(runtime, is_input); + spin_unlock_irq(&substream->lock); + kvfree(oldbuf); + } + runtime->avail_min = params->avail_min; + return 0; +} + +int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params) +{ + int err; + + snd_rawmidi_drain_output(substream); + mutex_lock(&substream->rmidi->open_mutex); + if (substream->append && substream->use_count > 1) + err = -EBUSY; + else + err = resize_runtime_buffer(substream, params, false); + + if (!err) + substream->active_sensing = !params->no_active_sensing; + mutex_unlock(&substream->rmidi->open_mutex); + return err; +} +EXPORT_SYMBOL(snd_rawmidi_output_params); + +int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_params *params) +{ + unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK; + unsigned int clock_type = params->mode & SNDRV_RAWMIDI_MODE_CLOCK_MASK; + int err; + + snd_rawmidi_drain_input(substream); + mutex_lock(&substream->rmidi->open_mutex); + if (framing == SNDRV_RAWMIDI_MODE_FRAMING_NONE && clock_type != SNDRV_RAWMIDI_MODE_CLOCK_NONE) + err = -EINVAL; + else if (clock_type > SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW) + err = -EINVAL; + else if (framing > SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) + err = -EINVAL; + else + err = resize_runtime_buffer(substream, params, true); + + if (!err) { + substream->framing = framing; + substream->clock_type = clock_type; + } + mutex_unlock(&substream->rmidi->open_mutex); + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_input_params); + +static int snd_rawmidi_output_status(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_status64 *status) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + memset(status, 0, sizeof(*status)); + status->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + spin_lock_irq(&substream->lock); + status->avail = runtime->avail; + spin_unlock_irq(&substream->lock); + return 0; +} + +static int snd_rawmidi_input_status(struct snd_rawmidi_substream *substream, + struct snd_rawmidi_status64 *status) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + memset(status, 0, sizeof(*status)); + status->stream = SNDRV_RAWMIDI_STREAM_INPUT; + spin_lock_irq(&substream->lock); + status->avail = runtime->avail; + status->xruns = runtime->xruns; + runtime->xruns = 0; + spin_unlock_irq(&substream->lock); + return 0; +} + +static int snd_rawmidi_ioctl_status32(struct snd_rawmidi_file *rfile, + struct snd_rawmidi_status32 __user *argp) +{ + int err = 0; + struct snd_rawmidi_status32 __user *status = argp; + struct snd_rawmidi_status32 status32; + struct snd_rawmidi_status64 status64; + + if (copy_from_user(&status32, argp, + sizeof(struct snd_rawmidi_status32))) + return -EFAULT; + + switch (status32.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + err = snd_rawmidi_output_status(rfile->output, &status64); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + err = snd_rawmidi_input_status(rfile->input, &status64); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + + status32 = (struct snd_rawmidi_status32) { + .stream = status64.stream, + .tstamp_sec = status64.tstamp_sec, + .tstamp_nsec = status64.tstamp_nsec, + .avail = status64.avail, + .xruns = status64.xruns, + }; + + if (copy_to_user(status, &status32, sizeof(*status))) + return -EFAULT; + + return 0; +} + +static int snd_rawmidi_ioctl_status64(struct snd_rawmidi_file *rfile, + struct snd_rawmidi_status64 __user *argp) +{ + int err = 0; + struct snd_rawmidi_status64 status; + + if (copy_from_user(&status, argp, sizeof(struct snd_rawmidi_status64))) + return -EFAULT; + + switch (status.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + err = snd_rawmidi_output_status(rfile->output, &status); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + err = snd_rawmidi_input_status(rfile->input, &status); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + if (copy_to_user(argp, &status, + sizeof(struct snd_rawmidi_status64))) + return -EFAULT; + return 0; +} + +static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_rawmidi_file *rfile; + struct snd_rawmidi *rmidi; + void __user *argp = (void __user *)arg; + + rfile = file->private_data; + if (((cmd >> 8) & 0xff) != 'W') + return -ENOTTY; + switch (cmd) { + case SNDRV_RAWMIDI_IOCTL_PVERSION: + return put_user(SNDRV_RAWMIDI_VERSION, (int __user *)argp) ? -EFAULT : 0; + case SNDRV_RAWMIDI_IOCTL_INFO: + { + int stream; + struct snd_rawmidi_info __user *info = argp; + + if (get_user(stream, &info->stream)) + return -EFAULT; + switch (stream) { + case SNDRV_RAWMIDI_STREAM_INPUT: + return snd_rawmidi_info_user(rfile->input, info); + case SNDRV_RAWMIDI_STREAM_OUTPUT: + return snd_rawmidi_info_user(rfile->output, info); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_USER_PVERSION: + if (get_user(rfile->user_pversion, (unsigned int __user *)arg)) + return -EFAULT; + return 0; + + case SNDRV_RAWMIDI_IOCTL_PARAMS: + { + struct snd_rawmidi_params params; + + if (copy_from_user(¶ms, argp, sizeof(struct snd_rawmidi_params))) + return -EFAULT; + if (rfile->user_pversion < SNDRV_PROTOCOL_VERSION(2, 0, 2)) { + params.mode = 0; + memset(params.reserved, 0, sizeof(params.reserved)); + } + switch (params.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_output_params(rfile->output, ¶ms); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + return snd_rawmidi_input_params(rfile->input, ¶ms); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_STATUS32: + return snd_rawmidi_ioctl_status32(rfile, argp); + case SNDRV_RAWMIDI_IOCTL_STATUS64: + return snd_rawmidi_ioctl_status64(rfile, argp); + case SNDRV_RAWMIDI_IOCTL_DROP: + { + int val; + + if (get_user(val, (int __user *) argp)) + return -EFAULT; + switch (val) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_drop_output(rfile->output); + default: + return -EINVAL; + } + } + case SNDRV_RAWMIDI_IOCTL_DRAIN: + { + int val; + + if (get_user(val, (int __user *) argp)) + return -EFAULT; + switch (val) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (rfile->output == NULL) + return -EINVAL; + return snd_rawmidi_drain_output(rfile->output); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (rfile->input == NULL) + return -EINVAL; + return snd_rawmidi_drain_input(rfile->input); + default: + return -EINVAL; + } + } + default: + rmidi = rfile->rmidi; + if (rmidi->ops && rmidi->ops->ioctl) + return rmidi->ops->ioctl(rmidi, cmd, argp); + rmidi_dbg(rmidi, "rawmidi: unknown command = 0x%x\n", cmd); + } + return -ENOTTY; +} + +/* ioctl to find the next device; either legacy or UMP depending on @find_ump */ +static int snd_rawmidi_next_device(struct snd_card *card, int __user *argp, + bool find_ump) + +{ + struct snd_rawmidi *rmidi; + int device; + bool is_ump; + + if (get_user(device, argp)) + return -EFAULT; + if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */ + device = SNDRV_RAWMIDI_DEVICES - 1; + mutex_lock(®ister_mutex); + device = device < 0 ? 0 : device + 1; + for (; device < SNDRV_RAWMIDI_DEVICES; device++) { + rmidi = snd_rawmidi_search(card, device); + if (!rmidi) + continue; + is_ump = rawmidi_is_ump(rmidi); + if (find_ump == is_ump) + break; + } + if (device == SNDRV_RAWMIDI_DEVICES) + device = -1; + mutex_unlock(®ister_mutex); + if (put_user(device, argp)) + return -EFAULT; + return 0; +} + +#if IS_ENABLED(CONFIG_SND_UMP) +/* inquiry of UMP endpoint and block info via control API */ +static int snd_rawmidi_call_ump_ioctl(struct snd_card *card, int cmd, + void __user *argp) +{ + struct snd_ump_endpoint_info __user *info = argp; + struct snd_rawmidi *rmidi; + int device, ret; + + if (get_user(device, &info->device)) + return -EFAULT; + mutex_lock(®ister_mutex); + rmidi = snd_rawmidi_search(card, device); + if (rmidi && rmidi->ops && rmidi->ops->ioctl) + ret = rmidi->ops->ioctl(rmidi, cmd, argp); + else + ret = -ENXIO; + mutex_unlock(®ister_mutex); + return ret; +} +#endif + +static int snd_rawmidi_control_ioctl(struct snd_card *card, + struct snd_ctl_file *control, + unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE: + return snd_rawmidi_next_device(card, argp, false); +#if IS_ENABLED(CONFIG_SND_UMP) + case SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE: + return snd_rawmidi_next_device(card, argp, true); + case SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO: + return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_ENDPOINT_INFO, argp); + case SNDRV_CTL_IOCTL_UMP_BLOCK_INFO: + return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_BLOCK_INFO, argp); +#endif + case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: + { + int val; + + if (get_user(val, (int __user *)argp)) + return -EFAULT; + control->preferred_subdevice[SND_CTL_SUBDEV_RAWMIDI] = val; + return 0; + } + case SNDRV_CTL_IOCTL_RAWMIDI_INFO: + return snd_rawmidi_info_select_user(card, argp); + } + return -ENOIOCTLCMD; +} + +static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream, + const unsigned char *buffer, int src_count, const struct timespec64 *tstamp) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_rawmidi_framing_tstamp *dest_ptr; + struct snd_rawmidi_framing_tstamp frame = { .tv_sec = tstamp->tv_sec, .tv_nsec = tstamp->tv_nsec }; + int orig_count = src_count; + int frame_size = sizeof(struct snd_rawmidi_framing_tstamp); + int align = get_align(runtime); + + BUILD_BUG_ON(frame_size != 0x20); + if (snd_BUG_ON((runtime->hw_ptr & 0x1f) != 0)) + return -EINVAL; + + while (src_count > align) { + if ((int)(runtime->buffer_size - runtime->avail) < frame_size) { + runtime->xruns += src_count; + break; + } + if (src_count >= SNDRV_RAWMIDI_FRAMING_DATA_LENGTH) + frame.length = SNDRV_RAWMIDI_FRAMING_DATA_LENGTH; + else { + frame.length = get_aligned_size(runtime, src_count); + if (!frame.length) + break; + memset(frame.data, 0, SNDRV_RAWMIDI_FRAMING_DATA_LENGTH); + } + memcpy(frame.data, buffer, frame.length); + buffer += frame.length; + src_count -= frame.length; + dest_ptr = (struct snd_rawmidi_framing_tstamp *) (runtime->buffer + runtime->hw_ptr); + *dest_ptr = frame; + runtime->avail += frame_size; + runtime->hw_ptr += frame_size; + runtime->hw_ptr %= runtime->buffer_size; + } + return orig_count - src_count; +} + +static struct timespec64 get_framing_tstamp(struct snd_rawmidi_substream *substream) +{ + struct timespec64 ts64 = {0, 0}; + + switch (substream->clock_type) { + case SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW: + ktime_get_raw_ts64(&ts64); + break; + case SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC: + ktime_get_ts64(&ts64); + break; + case SNDRV_RAWMIDI_MODE_CLOCK_REALTIME: + ktime_get_real_ts64(&ts64); + break; + } + return ts64; +} + +/** + * snd_rawmidi_receive - receive the input data from the device + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: the data size to read + * + * Reads the data from the internal buffer. + * + * Return: The size of read data, or a negative error code on failure. + */ +int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, + const unsigned char *buffer, int count) +{ + unsigned long flags; + struct timespec64 ts64 = get_framing_tstamp(substream); + int result = 0, count1; + struct snd_rawmidi_runtime *runtime; + + spin_lock_irqsave(&substream->lock, flags); + if (!substream->opened) { + result = -EBADFD; + goto unlock; + } + runtime = substream->runtime; + if (!runtime || !runtime->buffer) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_receive: input is not active!!!\n"); + result = -EINVAL; + goto unlock; + } + + count = get_aligned_size(runtime, count); + if (!count) + goto unlock; + + if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) { + result = receive_with_tstamp_framing(substream, buffer, count, &ts64); + } else if (count == 1) { /* special case, faster code */ + substream->bytes++; + if (runtime->avail < runtime->buffer_size) { + runtime->buffer[runtime->hw_ptr++] = buffer[0]; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail++; + result++; + } else { + runtime->xruns++; + } + } else { + substream->bytes += count; + count1 = runtime->buffer_size - runtime->hw_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) + count1 = runtime->buffer_size - runtime->avail; + count1 = get_aligned_size(runtime, count1); + if (!count1) + goto unlock; + memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1); + runtime->hw_ptr += count1; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail += count1; + count -= count1; + result += count1; + if (count > 0) { + buffer += count1; + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) { + count1 = runtime->buffer_size - runtime->avail; + runtime->xruns += count - count1; + } + if (count1 > 0) { + memcpy(runtime->buffer, buffer, count1); + runtime->hw_ptr = count1; + runtime->avail += count1; + result += count1; + } + } + } + if (result > 0) { + if (runtime->event) + schedule_work(&runtime->event_work); + else if (__snd_rawmidi_ready(runtime)) + wake_up(&runtime->sleep); + } + unlock: + spin_unlock_irqrestore(&substream->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_receive); + +static long snd_rawmidi_kernel_read1(struct snd_rawmidi_substream *substream, + unsigned char __user *userbuf, + unsigned char *kernelbuf, long count) +{ + unsigned long flags; + long result = 0, count1; + struct snd_rawmidi_runtime *runtime = substream->runtime; + unsigned long appl_ptr; + int err = 0; + + spin_lock_irqsave(&substream->lock, flags); + snd_rawmidi_buffer_ref(runtime); + while (count > 0 && runtime->avail) { + count1 = runtime->buffer_size - runtime->appl_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)runtime->avail) + count1 = runtime->avail; + + /* update runtime->appl_ptr before unlocking for userbuf */ + appl_ptr = runtime->appl_ptr; + runtime->appl_ptr += count1; + runtime->appl_ptr %= runtime->buffer_size; + runtime->avail -= count1; + + if (kernelbuf) + memcpy(kernelbuf + result, runtime->buffer + appl_ptr, count1); + if (userbuf) { + spin_unlock_irqrestore(&substream->lock, flags); + if (copy_to_user(userbuf + result, + runtime->buffer + appl_ptr, count1)) + err = -EFAULT; + spin_lock_irqsave(&substream->lock, flags); + if (err) + goto out; + } + result += count1; + count -= count1; + } + out: + snd_rawmidi_buffer_unref(runtime); + spin_unlock_irqrestore(&substream->lock, flags); + return result > 0 ? result : err; +} + +long snd_rawmidi_kernel_read(struct snd_rawmidi_substream *substream, + unsigned char *buf, long count) +{ + snd_rawmidi_input_trigger(substream, 1); + return snd_rawmidi_kernel_read1(substream, NULL/*userbuf*/, buf, count); +} +EXPORT_SYMBOL(snd_rawmidi_kernel_read); + +static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t count, + loff_t *offset) +{ + long result; + int count1; + struct snd_rawmidi_file *rfile; + struct snd_rawmidi_substream *substream; + struct snd_rawmidi_runtime *runtime; + + rfile = file->private_data; + substream = rfile->input; + if (substream == NULL) + return -EIO; + runtime = substream->runtime; + snd_rawmidi_input_trigger(substream, 1); + result = 0; + while (count > 0) { + spin_lock_irq(&substream->lock); + while (!__snd_rawmidi_ready(runtime)) { + wait_queue_entry_t wait; + + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + spin_unlock_irq(&substream->lock); + return result > 0 ? result : -EAGAIN; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&substream->lock); + schedule(); + remove_wait_queue(&runtime->sleep, &wait); + if (rfile->rmidi->card->shutdown) + return -ENODEV; + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + spin_lock_irq(&substream->lock); + if (!runtime->avail) { + spin_unlock_irq(&substream->lock); + return result > 0 ? result : -EIO; + } + } + spin_unlock_irq(&substream->lock); + count1 = snd_rawmidi_kernel_read1(substream, + (unsigned char __user *)buf, + NULL/*kernelbuf*/, + count); + if (count1 < 0) + return result > 0 ? result : count1; + result += count1; + buf += count1; + count -= count1; + } + return result; +} + +/** + * snd_rawmidi_transmit_empty - check whether the output buffer is empty + * @substream: the rawmidi substream + * + * Return: 1 if the internal output buffer is empty, 0 if not. + */ +int snd_rawmidi_transmit_empty(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime; + int result; + unsigned long flags; + + spin_lock_irqsave(&substream->lock, flags); + runtime = substream->runtime; + if (!substream->opened || !runtime || !runtime->buffer) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_transmit_empty: output is not active!!!\n"); + result = 1; + } else { + result = runtime->avail >= runtime->buffer_size; + } + spin_unlock_irqrestore(&substream->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit_empty); + +/* + * __snd_rawmidi_transmit_peek - copy data from the internal buffer + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: data size to transfer + * + * This is a variant of snd_rawmidi_transmit_peek() without spinlock. + */ +static int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, + unsigned char *buffer, int count) +{ + int result, count1; + struct snd_rawmidi_runtime *runtime = substream->runtime; + + if (runtime->buffer == NULL) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_transmit_peek: output is not active!!!\n"); + return -EINVAL; + } + result = 0; + if (runtime->avail >= runtime->buffer_size) { + /* warning: lowlevel layer MUST trigger down the hardware */ + goto __skip; + } + if (count == 1) { /* special case, faster code */ + *buffer = runtime->buffer[runtime->hw_ptr]; + result++; + } else { + count1 = runtime->buffer_size - runtime->hw_ptr; + if (count1 > count) + count1 = count; + if (count1 > (int)(runtime->buffer_size - runtime->avail)) + count1 = runtime->buffer_size - runtime->avail; + count1 = get_aligned_size(runtime, count1); + if (!count1) + goto __skip; + memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1); + count -= count1; + result += count1; + if (count > 0) { + if (count > (int)(runtime->buffer_size - runtime->avail - count1)) + count = runtime->buffer_size - runtime->avail - count1; + count = get_aligned_size(runtime, count); + if (!count) + goto __skip; + memcpy(buffer + count1, runtime->buffer, count); + result += count; + } + } + __skip: + return result; +} + +/** + * snd_rawmidi_transmit_peek - copy data from the internal buffer + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: data size to transfer + * + * Copies data from the internal output buffer to the given buffer. + * + * Call this in the interrupt handler when the midi output is ready, + * and call snd_rawmidi_transmit_ack() after the transmission is + * finished. + * + * Return: The size of copied data, or a negative error code on failure. + */ +int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, + unsigned char *buffer, int count) +{ + int result; + unsigned long flags; + + spin_lock_irqsave(&substream->lock, flags); + if (!substream->opened || !substream->runtime) + result = -EBADFD; + else + result = __snd_rawmidi_transmit_peek(substream, buffer, count); + spin_unlock_irqrestore(&substream->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit_peek); + +/* + * __snd_rawmidi_transmit_ack - acknowledge the transmission + * @substream: the rawmidi substream + * @count: the transferred count + * + * This is a variant of __snd_rawmidi_transmit_ack() without spinlock. + */ +static int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, + int count) +{ + struct snd_rawmidi_runtime *runtime = substream->runtime; + + if (runtime->buffer == NULL) { + rmidi_dbg(substream->rmidi, + "snd_rawmidi_transmit_ack: output is not active!!!\n"); + return -EINVAL; + } + snd_BUG_ON(runtime->avail + count > runtime->buffer_size); + count = get_aligned_size(runtime, count); + runtime->hw_ptr += count; + runtime->hw_ptr %= runtime->buffer_size; + runtime->avail += count; + substream->bytes += count; + if (count > 0) { + if (runtime->drain || __snd_rawmidi_ready(runtime)) + wake_up(&runtime->sleep); + } + return count; +} + +/** + * snd_rawmidi_transmit_ack - acknowledge the transmission + * @substream: the rawmidi substream + * @count: the transferred count + * + * Advances the hardware pointer for the internal output buffer with + * the given size and updates the condition. + * Call after the transmission is finished. + * + * Return: The advanced size if successful, or a negative error code on failure. + */ +int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) +{ + int result; + unsigned long flags; + + spin_lock_irqsave(&substream->lock, flags); + if (!substream->opened || !substream->runtime) + result = -EBADFD; + else + result = __snd_rawmidi_transmit_ack(substream, count); + spin_unlock_irqrestore(&substream->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit_ack); + +/** + * snd_rawmidi_transmit - copy from the buffer to the device + * @substream: the rawmidi substream + * @buffer: the buffer pointer + * @count: the data size to transfer + * + * Copies data from the buffer to the device and advances the pointer. + * + * Return: The copied size if successful, or a negative error code on failure. + */ +int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, + unsigned char *buffer, int count) +{ + int result; + unsigned long flags; + + spin_lock_irqsave(&substream->lock, flags); + if (!substream->opened) + result = -EBADFD; + else { + count = __snd_rawmidi_transmit_peek(substream, buffer, count); + if (count <= 0) + result = count; + else + result = __snd_rawmidi_transmit_ack(substream, count); + } + spin_unlock_irqrestore(&substream->lock, flags); + return result; +} +EXPORT_SYMBOL(snd_rawmidi_transmit); + +/** + * snd_rawmidi_proceed - Discard the all pending bytes and proceed + * @substream: rawmidi substream + * + * Return: the number of discarded bytes + */ +int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime; + unsigned long flags; + int count = 0; + + spin_lock_irqsave(&substream->lock, flags); + runtime = substream->runtime; + if (substream->opened && runtime && + runtime->avail < runtime->buffer_size) { + count = runtime->buffer_size - runtime->avail; + __snd_rawmidi_transmit_ack(substream, count); + } + spin_unlock_irqrestore(&substream->lock, flags); + return count; +} +EXPORT_SYMBOL(snd_rawmidi_proceed); + +static long snd_rawmidi_kernel_write1(struct snd_rawmidi_substream *substream, + const unsigned char __user *userbuf, + const unsigned char *kernelbuf, + long count) +{ + unsigned long flags; + long count1, result; + struct snd_rawmidi_runtime *runtime = substream->runtime; + unsigned long appl_ptr; + + if (!kernelbuf && !userbuf) + return -EINVAL; + if (snd_BUG_ON(!runtime->buffer)) + return -EINVAL; + + result = 0; + spin_lock_irqsave(&substream->lock, flags); + if (substream->append) { + if ((long)runtime->avail < count) { + spin_unlock_irqrestore(&substream->lock, flags); + return -EAGAIN; + } + } + snd_rawmidi_buffer_ref(runtime); + while (count > 0 && runtime->avail > 0) { + count1 = runtime->buffer_size - runtime->appl_ptr; + if (count1 > count) + count1 = count; + if (count1 > (long)runtime->avail) + count1 = runtime->avail; + + /* update runtime->appl_ptr before unlocking for userbuf */ + appl_ptr = runtime->appl_ptr; + runtime->appl_ptr += count1; + runtime->appl_ptr %= runtime->buffer_size; + runtime->avail -= count1; + + if (kernelbuf) + memcpy(runtime->buffer + appl_ptr, + kernelbuf + result, count1); + else if (userbuf) { + spin_unlock_irqrestore(&substream->lock, flags); + if (copy_from_user(runtime->buffer + appl_ptr, + userbuf + result, count1)) { + spin_lock_irqsave(&substream->lock, flags); + result = result > 0 ? result : -EFAULT; + goto __end; + } + spin_lock_irqsave(&substream->lock, flags); + } + result += count1; + count -= count1; + } + __end: + count1 = runtime->avail < runtime->buffer_size; + snd_rawmidi_buffer_unref(runtime); + spin_unlock_irqrestore(&substream->lock, flags); + if (count1) + snd_rawmidi_output_trigger(substream, 1); + return result; +} + +long snd_rawmidi_kernel_write(struct snd_rawmidi_substream *substream, + const unsigned char *buf, long count) +{ + return snd_rawmidi_kernel_write1(substream, NULL, buf, count); +} +EXPORT_SYMBOL(snd_rawmidi_kernel_write); + +static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + long result, timeout; + int count1; + struct snd_rawmidi_file *rfile; + struct snd_rawmidi_runtime *runtime; + struct snd_rawmidi_substream *substream; + + rfile = file->private_data; + substream = rfile->output; + runtime = substream->runtime; + /* we cannot put an atomic message to our buffer */ + if (substream->append && count > runtime->buffer_size) + return -EIO; + result = 0; + while (count > 0) { + spin_lock_irq(&substream->lock); + while (!snd_rawmidi_ready_append(substream, count)) { + wait_queue_entry_t wait; + + if (file->f_flags & O_NONBLOCK) { + spin_unlock_irq(&substream->lock); + return result > 0 ? result : -EAGAIN; + } + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&substream->lock); + timeout = schedule_timeout(30 * HZ); + remove_wait_queue(&runtime->sleep, &wait); + if (rfile->rmidi->card->shutdown) + return -ENODEV; + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + spin_lock_irq(&substream->lock); + if (!runtime->avail && !timeout) { + spin_unlock_irq(&substream->lock); + return result > 0 ? result : -EIO; + } + } + spin_unlock_irq(&substream->lock); + count1 = snd_rawmidi_kernel_write1(substream, buf, NULL, count); + if (count1 < 0) + return result > 0 ? result : count1; + result += count1; + buf += count1; + if ((size_t)count1 < count && (file->f_flags & O_NONBLOCK)) + break; + count -= count1; + } + if (file->f_flags & O_DSYNC) { + spin_lock_irq(&substream->lock); + while (runtime->avail != runtime->buffer_size) { + wait_queue_entry_t wait; + unsigned int last_avail = runtime->avail; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&runtime->sleep, &wait); + set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&substream->lock); + timeout = schedule_timeout(30 * HZ); + remove_wait_queue(&runtime->sleep, &wait); + if (signal_pending(current)) + return result > 0 ? result : -ERESTARTSYS; + if (runtime->avail == last_avail && !timeout) + return result > 0 ? result : -EIO; + spin_lock_irq(&substream->lock); + } + spin_unlock_irq(&substream->lock); + } + return result; +} + +static __poll_t snd_rawmidi_poll(struct file *file, poll_table *wait) +{ + struct snd_rawmidi_file *rfile; + struct snd_rawmidi_runtime *runtime; + __poll_t mask; + + rfile = file->private_data; + if (rfile->input != NULL) { + runtime = rfile->input->runtime; + snd_rawmidi_input_trigger(rfile->input, 1); + poll_wait(file, &runtime->sleep, wait); + } + if (rfile->output != NULL) { + runtime = rfile->output->runtime; + poll_wait(file, &runtime->sleep, wait); + } + mask = 0; + if (rfile->input != NULL) { + if (snd_rawmidi_ready(rfile->input)) + mask |= EPOLLIN | EPOLLRDNORM; + } + if (rfile->output != NULL) { + if (snd_rawmidi_ready(rfile->output)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + return mask; +} + +/* + */ +#ifdef CONFIG_COMPAT +#include "rawmidi_compat.c" +#else +#define snd_rawmidi_ioctl_compat NULL +#endif + +/* + */ + +static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream; + struct snd_rawmidi_runtime *runtime; + unsigned long buffer_size, avail, xruns; + unsigned int clock_type; + static const char *clock_names[4] = { "none", "realtime", "monotonic", "monotonic raw" }; + + rmidi = entry->private_data; + snd_iprintf(buffer, "%s\n\n", rmidi->name); + if (IS_ENABLED(CONFIG_SND_UMP)) + snd_iprintf(buffer, "Type: %s\n", + rawmidi_is_ump(rmidi) ? "UMP" : "Legacy"); + if (rmidi->ops && rmidi->ops->proc_read) + rmidi->ops->proc_read(entry, buffer); + mutex_lock(&rmidi->open_mutex); + if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) { + list_for_each_entry(substream, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams, + list) { + snd_iprintf(buffer, + "Output %d\n" + " Tx bytes : %lu\n", + substream->number, + (unsigned long) substream->bytes); + if (substream->opened) { + snd_iprintf(buffer, + " Owner PID : %d\n", + pid_vnr(substream->pid)); + runtime = substream->runtime; + spin_lock_irq(&substream->lock); + buffer_size = runtime->buffer_size; + avail = runtime->avail; + spin_unlock_irq(&substream->lock); + snd_iprintf(buffer, + " Mode : %s\n" + " Buffer size : %lu\n" + " Avail : %lu\n", + runtime->oss ? "OSS compatible" : "native", + buffer_size, avail); + } + } + } + if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_INPUT) { + list_for_each_entry(substream, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams, + list) { + snd_iprintf(buffer, + "Input %d\n" + " Rx bytes : %lu\n", + substream->number, + (unsigned long) substream->bytes); + if (substream->opened) { + snd_iprintf(buffer, + " Owner PID : %d\n", + pid_vnr(substream->pid)); + runtime = substream->runtime; + spin_lock_irq(&substream->lock); + buffer_size = runtime->buffer_size; + avail = runtime->avail; + xruns = runtime->xruns; + spin_unlock_irq(&substream->lock); + snd_iprintf(buffer, + " Buffer size : %lu\n" + " Avail : %lu\n" + " Overruns : %lu\n", + buffer_size, avail, xruns); + if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) { + clock_type = substream->clock_type >> SNDRV_RAWMIDI_MODE_CLOCK_SHIFT; + if (!snd_BUG_ON(clock_type >= ARRAY_SIZE(clock_names))) + snd_iprintf(buffer, + " Framing : tstamp\n" + " Clock type : %s\n", + clock_names[clock_type]); + } + } + } + } + mutex_unlock(&rmidi->open_mutex); +} + +/* + * Register functions + */ + +static const struct file_operations snd_rawmidi_f_ops = { + .owner = THIS_MODULE, + .read = snd_rawmidi_read, + .write = snd_rawmidi_write, + .open = snd_rawmidi_open, + .release = snd_rawmidi_release, + .llseek = no_llseek, + .poll = snd_rawmidi_poll, + .unlocked_ioctl = snd_rawmidi_ioctl, + .compat_ioctl = snd_rawmidi_ioctl_compat, +}; + +static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, + struct snd_rawmidi_str *stream, + int direction, + int count) +{ + struct snd_rawmidi_substream *substream; + int idx; + + for (idx = 0; idx < count; idx++) { + substream = kzalloc(sizeof(*substream), GFP_KERNEL); + if (!substream) + return -ENOMEM; + substream->stream = direction; + substream->number = idx; + substream->rmidi = rmidi; + substream->pstr = stream; + spin_lock_init(&substream->lock); + list_add_tail(&substream->list, &stream->substreams); + stream->substream_count++; + } + return 0; +} + +/* used for both rawmidi and ump */ +int snd_rawmidi_init(struct snd_rawmidi *rmidi, + struct snd_card *card, char *id, int device, + int output_count, int input_count, + unsigned int info_flags) +{ + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_rawmidi_dev_free, + .dev_register = snd_rawmidi_dev_register, + .dev_disconnect = snd_rawmidi_dev_disconnect, + }; + + rmidi->card = card; + rmidi->device = device; + mutex_init(&rmidi->open_mutex); + init_waitqueue_head(&rmidi->open_wait); + INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams); + INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams); + rmidi->info_flags = info_flags; + + if (id != NULL) + strscpy(rmidi->id, id, sizeof(rmidi->id)); + + err = snd_device_alloc(&rmidi->dev, card); + if (err < 0) + return err; + if (rawmidi_is_ump(rmidi)) + dev_set_name(rmidi->dev, "umpC%iD%i", card->number, device); + else + dev_set_name(rmidi->dev, "midiC%iD%i", card->number, device); + + err = snd_rawmidi_alloc_substreams(rmidi, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], + SNDRV_RAWMIDI_STREAM_INPUT, + input_count); + if (err < 0) + return err; + err = snd_rawmidi_alloc_substreams(rmidi, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], + SNDRV_RAWMIDI_STREAM_OUTPUT, + output_count); + if (err < 0) + return err; + err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops); + if (err < 0) + return err; + return 0; +} +EXPORT_SYMBOL_GPL(snd_rawmidi_init); + +/** + * snd_rawmidi_new - create a rawmidi instance + * @card: the card instance + * @id: the id string + * @device: the device index + * @output_count: the number of output streams + * @input_count: the number of input streams + * @rrawmidi: the pointer to store the new rawmidi instance + * + * Creates a new rawmidi instance. + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_rawmidi_new(struct snd_card *card, char *id, int device, + int output_count, int input_count, + struct snd_rawmidi **rrawmidi) +{ + struct snd_rawmidi *rmidi; + int err; + + if (rrawmidi) + *rrawmidi = NULL; + rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); + if (!rmidi) + return -ENOMEM; + err = snd_rawmidi_init(rmidi, card, id, device, + output_count, input_count, 0); + if (err < 0) { + snd_rawmidi_free(rmidi); + return err; + } + if (rrawmidi) + *rrawmidi = rmidi; + return 0; +} +EXPORT_SYMBOL(snd_rawmidi_new); + +static void snd_rawmidi_free_substreams(struct snd_rawmidi_str *stream) +{ + struct snd_rawmidi_substream *substream; + + while (!list_empty(&stream->substreams)) { + substream = list_entry(stream->substreams.next, struct snd_rawmidi_substream, list); + list_del(&substream->list); + kfree(substream); + } +} + +/* called from ump.c, too */ +int snd_rawmidi_free(struct snd_rawmidi *rmidi) +{ + if (!rmidi) + return 0; + + snd_info_free_entry(rmidi->proc_entry); + rmidi->proc_entry = NULL; + if (rmidi->ops && rmidi->ops->dev_unregister) + rmidi->ops->dev_unregister(rmidi); + + snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]); + snd_rawmidi_free_substreams(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]); + if (rmidi->private_free) + rmidi->private_free(rmidi); + put_device(rmidi->dev); + kfree(rmidi); + return 0; +} +EXPORT_SYMBOL_GPL(snd_rawmidi_free); + +static int snd_rawmidi_dev_free(struct snd_device *device) +{ + struct snd_rawmidi *rmidi = device->device_data; + + return snd_rawmidi_free(rmidi); +} + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +static void snd_rawmidi_dev_seq_free(struct snd_seq_device *device) +{ + struct snd_rawmidi *rmidi = device->private_data; + + rmidi->seq_dev = NULL; +} +#endif + +static int snd_rawmidi_dev_register(struct snd_device *device) +{ + int err; + struct snd_info_entry *entry; + char name[16]; + struct snd_rawmidi *rmidi = device->device_data; + + if (rmidi->device >= SNDRV_RAWMIDI_DEVICES) + return -ENOMEM; + err = 0; + mutex_lock(®ister_mutex); + if (snd_rawmidi_search(rmidi->card, rmidi->device)) + err = -EBUSY; + else + list_add_tail(&rmidi->list, &snd_rawmidi_devices); + mutex_unlock(®ister_mutex); + if (err < 0) + return err; + + err = snd_register_device(SNDRV_DEVICE_TYPE_RAWMIDI, + rmidi->card, rmidi->device, + &snd_rawmidi_f_ops, rmidi, rmidi->dev); + if (err < 0) { + rmidi_err(rmidi, "unable to register\n"); + goto error; + } + if (rmidi->ops && rmidi->ops->dev_register) { + err = rmidi->ops->dev_register(rmidi); + if (err < 0) + goto error_unregister; + } +#ifdef CONFIG_SND_OSSEMUL + rmidi->ossreg = 0; + if (!rawmidi_is_ump(rmidi) && + (int)rmidi->device == midi_map[rmidi->card->number]) { + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, + rmidi->card, 0, &snd_rawmidi_f_ops, + rmidi) < 0) { + rmidi_err(rmidi, + "unable to register OSS rawmidi device %i:%i\n", + rmidi->card->number, 0); + } else { + rmidi->ossreg++; +#ifdef SNDRV_OSS_INFO_DEV_MIDI + snd_oss_info_register(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number, rmidi->name); +#endif + } + } + if (!rawmidi_is_ump(rmidi) && + (int)rmidi->device == amidi_map[rmidi->card->number]) { + if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, + rmidi->card, 1, &snd_rawmidi_f_ops, + rmidi) < 0) { + rmidi_err(rmidi, + "unable to register OSS rawmidi device %i:%i\n", + rmidi->card->number, 1); + } else { + rmidi->ossreg++; + } + } +#endif /* CONFIG_SND_OSSEMUL */ + sprintf(name, "midi%d", rmidi->device); + entry = snd_info_create_card_entry(rmidi->card, name, rmidi->card->proc_root); + if (entry) { + entry->private_data = rmidi; + entry->c.text.read = snd_rawmidi_proc_info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + rmidi->proc_entry = entry; +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + /* no own registration mechanism? */ + if (!rmidi->ops || !rmidi->ops->dev_register) { + if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) { + rmidi->seq_dev->private_data = rmidi; + rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free; + sprintf(rmidi->seq_dev->name, "MIDI %d-%d", rmidi->card->number, rmidi->device); + snd_device_register(rmidi->card, rmidi->seq_dev); + } + } +#endif + return 0; + + error_unregister: + snd_unregister_device(rmidi->dev); + error: + mutex_lock(®ister_mutex); + list_del(&rmidi->list); + mutex_unlock(®ister_mutex); + return err; +} + +static int snd_rawmidi_dev_disconnect(struct snd_device *device) +{ + struct snd_rawmidi *rmidi = device->device_data; + int dir; + + mutex_lock(®ister_mutex); + mutex_lock(&rmidi->open_mutex); + wake_up(&rmidi->open_wait); + list_del_init(&rmidi->list); + for (dir = 0; dir < 2; dir++) { + struct snd_rawmidi_substream *s; + + list_for_each_entry(s, &rmidi->streams[dir].substreams, list) { + if (s->runtime) + wake_up(&s->runtime->sleep); + } + } + +#ifdef CONFIG_SND_OSSEMUL + if (rmidi->ossreg) { + if ((int)rmidi->device == midi_map[rmidi->card->number]) { + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 0); +#ifdef SNDRV_OSS_INFO_DEV_MIDI + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_MIDI, rmidi->card->number); +#endif + } + if ((int)rmidi->device == amidi_map[rmidi->card->number]) + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 1); + rmidi->ossreg = 0; + } +#endif /* CONFIG_SND_OSSEMUL */ + snd_unregister_device(rmidi->dev); + mutex_unlock(&rmidi->open_mutex); + mutex_unlock(®ister_mutex); + return 0; +} + +/** + * snd_rawmidi_set_ops - set the rawmidi operators + * @rmidi: the rawmidi instance + * @stream: the stream direction, SNDRV_RAWMIDI_STREAM_XXX + * @ops: the operator table + * + * Sets the rawmidi operators for the given stream direction. + */ +void snd_rawmidi_set_ops(struct snd_rawmidi *rmidi, int stream, + const struct snd_rawmidi_ops *ops) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &rmidi->streams[stream].substreams, list) + substream->ops = ops; +} +EXPORT_SYMBOL(snd_rawmidi_set_ops); + +/* + * ENTRY functions + */ + +static int __init alsa_rawmidi_init(void) +{ + + snd_ctl_register_ioctl(snd_rawmidi_control_ioctl); + snd_ctl_register_ioctl_compat(snd_rawmidi_control_ioctl); +#ifdef CONFIG_SND_OSSEMUL + { int i; + /* check device map table */ + for (i = 0; i < SNDRV_CARDS; i++) { + if (midi_map[i] < 0 || midi_map[i] >= SNDRV_RAWMIDI_DEVICES) { + pr_err("ALSA: rawmidi: invalid midi_map[%d] = %d\n", + i, midi_map[i]); + midi_map[i] = 0; + } + if (amidi_map[i] < 0 || amidi_map[i] >= SNDRV_RAWMIDI_DEVICES) { + pr_err("ALSA: rawmidi: invalid amidi_map[%d] = %d\n", + i, amidi_map[i]); + amidi_map[i] = 1; + } + } + } +#endif /* CONFIG_SND_OSSEMUL */ + return 0; +} + +static void __exit alsa_rawmidi_exit(void) +{ + snd_ctl_unregister_ioctl(snd_rawmidi_control_ioctl); + snd_ctl_unregister_ioctl_compat(snd_rawmidi_control_ioctl); +} + +module_init(alsa_rawmidi_init) +module_exit(alsa_rawmidi_exit) diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c new file mode 100644 index 0000000000..b81b30d82f --- /dev/null +++ b/sound/core/rawmidi_compat.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 32bit -> 64bit ioctl wrapper for raw MIDI API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +/* This file included from rawmidi.c */ + +#include <linux/compat.h> + +struct snd_rawmidi_params32 { + s32 stream; + u32 buffer_size; + u32 avail_min; + unsigned int no_active_sensing; /* avoid bit-field */ + unsigned int mode; + unsigned char reserved[12]; +} __attribute__((packed)); + +static int snd_rawmidi_ioctl_params_compat(struct snd_rawmidi_file *rfile, + struct snd_rawmidi_params32 __user *src) +{ + struct snd_rawmidi_params params; + unsigned int val; + + if (get_user(params.stream, &src->stream) || + get_user(params.buffer_size, &src->buffer_size) || + get_user(params.avail_min, &src->avail_min) || + get_user(params.mode, &src->mode) || + get_user(val, &src->no_active_sensing)) + return -EFAULT; + params.no_active_sensing = val; + switch (params.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (!rfile->output) + return -EINVAL; + return snd_rawmidi_output_params(rfile->output, ¶ms); + case SNDRV_RAWMIDI_STREAM_INPUT: + if (!rfile->input) + return -EINVAL; + return snd_rawmidi_input_params(rfile->input, ¶ms); + } + return -EINVAL; +} + +struct compat_snd_rawmidi_status64 { + s32 stream; + u8 rsvd[4]; /* alignment */ + s64 tstamp_sec; + s64 tstamp_nsec; + u32 avail; + u32 xruns; + unsigned char reserved[16]; +} __attribute__((packed)); + +static int snd_rawmidi_ioctl_status_compat64(struct snd_rawmidi_file *rfile, + struct compat_snd_rawmidi_status64 __user *src) +{ + int err; + struct snd_rawmidi_status64 status; + struct compat_snd_rawmidi_status64 compat_status; + + if (get_user(status.stream, &src->stream)) + return -EFAULT; + + switch (status.stream) { + case SNDRV_RAWMIDI_STREAM_OUTPUT: + if (!rfile->output) + return -EINVAL; + err = snd_rawmidi_output_status(rfile->output, &status); + break; + case SNDRV_RAWMIDI_STREAM_INPUT: + if (!rfile->input) + return -EINVAL; + err = snd_rawmidi_input_status(rfile->input, &status); + break; + default: + return -EINVAL; + } + if (err < 0) + return err; + + compat_status = (struct compat_snd_rawmidi_status64) { + .stream = status.stream, + .tstamp_sec = status.tstamp_sec, + .tstamp_nsec = status.tstamp_nsec, + .avail = status.avail, + .xruns = status.xruns, + }; + + if (copy_to_user(src, &compat_status, sizeof(*src))) + return -EFAULT; + + return 0; +} + +enum { + SNDRV_RAWMIDI_IOCTL_PARAMS32 = _IOWR('W', 0x10, struct snd_rawmidi_params32), + SNDRV_RAWMIDI_IOCTL_STATUS_COMPAT32 = _IOWR('W', 0x20, struct snd_rawmidi_status32), + SNDRV_RAWMIDI_IOCTL_STATUS_COMPAT64 = _IOWR('W', 0x20, struct compat_snd_rawmidi_status64), +}; + +static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_rawmidi_file *rfile; + void __user *argp = compat_ptr(arg); + + rfile = file->private_data; + switch (cmd) { + case SNDRV_RAWMIDI_IOCTL_PVERSION: + case SNDRV_RAWMIDI_IOCTL_INFO: + case SNDRV_RAWMIDI_IOCTL_DROP: + case SNDRV_RAWMIDI_IOCTL_DRAIN: +#if IS_ENABLED(CONFIG_SND_UMP) + case SNDRV_UMP_IOCTL_ENDPOINT_INFO: + case SNDRV_UMP_IOCTL_BLOCK_INFO: +#endif + return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp); + case SNDRV_RAWMIDI_IOCTL_PARAMS32: + return snd_rawmidi_ioctl_params_compat(rfile, argp); + case SNDRV_RAWMIDI_IOCTL_STATUS_COMPAT32: + return snd_rawmidi_ioctl_status32(rfile, argp); + case SNDRV_RAWMIDI_IOCTL_STATUS_COMPAT64: + return snd_rawmidi_ioctl_status_compat64(rfile, argp); + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig new file mode 100644 index 0000000000..c14981daf9 --- /dev/null +++ b/sound/core/seq/Kconfig @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_SEQUENCER + tristate "Sequencer support" + select SND_TIMER + select SND_SEQ_DEVICE + help + Say Y or M to enable MIDI sequencer and router support. This + feature allows routing and enqueueing of MIDI events. Events + can be processed at a given time. + + Many programs require this feature, so you should enable it + unless you know what you're doing. + +if SND_SEQUENCER + +config SND_SEQ_DUMMY + tristate "Sequencer dummy client" + help + Say Y here to enable the dummy sequencer client. This client + is a simple MIDI-through client: all normal input events are + redirected to the output port immediately. + + You don't need this unless you want to connect many MIDI + devices or applications together. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-dummy. + +config SND_SEQUENCER_OSS + tristate "OSS Sequencer API" + depends on SND_OSSEMUL + select SND_SEQ_MIDI_EVENT + help + Say Y here to enable OSS sequencer emulation (both + /dev/sequencer and /dev/music interfaces). + + Many programs still use the OSS API, so say Y. + + To compile this driver as a module, choose M here: the module + will be called snd-seq-oss. + +config SND_SEQ_HRTIMER_DEFAULT + bool "Use HR-timer as default sequencer timer" + depends on SND_HRTIMER + default y + help + Say Y here to use the HR-timer backend as the default sequencer + timer. + +config SND_SEQ_MIDI_EVENT + tristate + +config SND_SEQ_MIDI + def_tristate SND_RAWMIDI + select SND_SEQ_MIDI_EVENT + +config SND_SEQ_MIDI_EMUL + tristate + +config SND_SEQ_VIRMIDI + tristate + +config SND_SEQ_UMP + bool "Support for UMP events" + default y if SND_SEQ_UMP_CLIENT + help + Say Y here to enable the support for handling UMP (Universal MIDI + Packet) events via ALSA sequencer infrastructure, which is an + essential feature for enabling MIDI 2.0 support. + It includes the automatic conversion of ALSA sequencer events + among legacy and UMP clients. + +config SND_SEQ_UMP_CLIENT + tristate + def_tristate SND_UMP + +endif # SND_SEQUENCER diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile new file mode 100644 index 0000000000..990eec7c83 --- /dev/null +++ b/sound/core/seq/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> +# + +snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \ + seq_fifo.o seq_prioq.o seq_timer.o \ + seq_system.o seq_ports.o +snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o +snd-seq-$(CONFIG_SND_SEQ_UMP) += seq_ump_convert.o +snd-seq-midi-objs := seq_midi.o +snd-seq-midi-emul-objs := seq_midi_emul.o +snd-seq-midi-event-objs := seq_midi_event.o +snd-seq-dummy-objs := seq_dummy.o +snd-seq-virmidi-objs := seq_virmidi.o +snd-seq-ump-client-objs := seq_ump_client.o + +obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o +obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/ + +obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o +obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o +obj-$(CONFIG_SND_SEQ_UMP_CLIENT) += snd-seq-ump-client.o +obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o +obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o +obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o diff --git a/sound/core/seq/oss/Makefile b/sound/core/seq/oss/Makefile new file mode 100644 index 0000000000..f1a6087854 --- /dev/null +++ b/sound/core/seq/oss/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 1999 by Jaroslav Kysela <perex@perex.cz> +# + +snd-seq-oss-objs := seq_oss.o seq_oss_init.o seq_oss_timer.o seq_oss_ioctl.o \ + seq_oss_event.o seq_oss_rw.o seq_oss_synth.o \ + seq_oss_midi.o seq_oss_readq.o seq_oss_writeq.o + +obj-$(CONFIG_SND_SEQUENCER_OSS) += snd-seq-oss.o diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c new file mode 100644 index 0000000000..77c1214acd --- /dev/null +++ b/sound/core/seq/oss/seq_oss.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * registration of device and proc + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/compat.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/initval.h> +#include "seq_oss_device.h" +#include "seq_oss_synth.h" + +/* + * module option + */ +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("OSS-compatible sequencer module"); +MODULE_LICENSE("GPL"); +/* Takashi says this is really only for sound-service-0-, but this is OK. */ +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_SEQUENCER); +MODULE_ALIAS_SNDRV_MINOR(SNDRV_MINOR_OSS_MUSIC); + + +/* + * prototypes + */ +static int register_device(void); +static void unregister_device(void); +#ifdef CONFIG_SND_PROC_FS +static int register_proc(void); +static void unregister_proc(void); +#else +static inline int register_proc(void) { return 0; } +static inline void unregister_proc(void) {} +#endif + +static int odev_open(struct inode *inode, struct file *file); +static int odev_release(struct inode *inode, struct file *file); +static ssize_t odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset); +static ssize_t odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset); +static long odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static __poll_t odev_poll(struct file *file, poll_table * wait); + + +/* + * module interface + */ + +static struct snd_seq_driver seq_oss_synth_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_oss_synth_probe, + .remove = snd_seq_oss_synth_remove, + }, + .id = SNDRV_SEQ_DEV_ID_OSS, + .argsize = sizeof(struct snd_seq_oss_reg), +}; + +static int __init alsa_seq_oss_init(void) +{ + int rc; + + rc = register_device(); + if (rc < 0) + goto error; + rc = register_proc(); + if (rc < 0) { + unregister_device(); + goto error; + } + rc = snd_seq_oss_create_client(); + if (rc < 0) { + unregister_proc(); + unregister_device(); + goto error; + } + + rc = snd_seq_driver_register(&seq_oss_synth_driver); + if (rc < 0) { + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); + goto error; + } + + /* success */ + snd_seq_oss_synth_init(); + + error: + return rc; +} + +static void __exit alsa_seq_oss_exit(void) +{ + snd_seq_driver_unregister(&seq_oss_synth_driver); + snd_seq_oss_delete_client(); + unregister_proc(); + unregister_device(); +} + +module_init(alsa_seq_oss_init) +module_exit(alsa_seq_oss_exit) + +/* + * ALSA minor device interface + */ + +static DEFINE_MUTEX(register_mutex); + +static int +odev_open(struct inode *inode, struct file *file) +{ + int level, rc; + + if (iminor(inode) == SNDRV_MINOR_OSS_MUSIC) + level = SNDRV_SEQ_OSS_MODE_MUSIC; + else + level = SNDRV_SEQ_OSS_MODE_SYNTH; + + mutex_lock(®ister_mutex); + rc = snd_seq_oss_open(file, level); + mutex_unlock(®ister_mutex); + + return rc; +} + +static int +odev_release(struct inode *inode, struct file *file) +{ + struct seq_oss_devinfo *dp; + + dp = file->private_data; + if (!dp) + return 0; + + mutex_lock(®ister_mutex); + snd_seq_oss_release(dp); + mutex_unlock(®ister_mutex); + + return 0; +} + +static ssize_t +odev_read(struct file *file, char __user *buf, size_t count, loff_t *offset) +{ + struct seq_oss_devinfo *dp; + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return -ENXIO; + return snd_seq_oss_read(dp, buf, count); +} + + +static ssize_t +odev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) +{ + struct seq_oss_devinfo *dp; + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return -ENXIO; + return snd_seq_oss_write(dp, buf, count, file); +} + +static long +odev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct seq_oss_devinfo *dp; + long rc; + + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return -ENXIO; + + if (cmd != SNDCTL_SEQ_SYNC && + mutex_lock_interruptible(®ister_mutex)) + return -ERESTARTSYS; + rc = snd_seq_oss_ioctl(dp, cmd, arg); + if (cmd != SNDCTL_SEQ_SYNC) + mutex_unlock(®ister_mutex); + return rc; +} + +#ifdef CONFIG_COMPAT +static long odev_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return odev_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define odev_ioctl_compat NULL +#endif + +static __poll_t +odev_poll(struct file *file, poll_table * wait) +{ + struct seq_oss_devinfo *dp; + dp = file->private_data; + if (snd_BUG_ON(!dp)) + return EPOLLERR; + return snd_seq_oss_poll(dp, file, wait); +} + +/* + * registration of sequencer minor device + */ + +static const struct file_operations seq_oss_f_ops = +{ + .owner = THIS_MODULE, + .read = odev_read, + .write = odev_write, + .open = odev_open, + .release = odev_release, + .poll = odev_poll, + .unlocked_ioctl = odev_ioctl, + .compat_ioctl = odev_ioctl_compat, + .llseek = noop_llseek, +}; + +static int __init +register_device(void) +{ + int rc; + + mutex_lock(®ister_mutex); + rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, + NULL, 0, + &seq_oss_f_ops, NULL); + if (rc < 0) { + pr_err("ALSA: seq_oss: can't register device seq\n"); + mutex_unlock(®ister_mutex); + return rc; + } + rc = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, + NULL, 0, + &seq_oss_f_ops, NULL); + if (rc < 0) { + pr_err("ALSA: seq_oss: can't register device music\n"); + snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0); + mutex_unlock(®ister_mutex); + return rc; + } + mutex_unlock(®ister_mutex); + return 0; +} + +static void +unregister_device(void) +{ + mutex_lock(®ister_mutex); + if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_MUSIC, NULL, 0) < 0) + pr_err("ALSA: seq_oss: error unregister device music\n"); + if (snd_unregister_oss_device(SNDRV_OSS_DEVICE_TYPE_SEQUENCER, NULL, 0) < 0) + pr_err("ALSA: seq_oss: error unregister device seq\n"); + mutex_unlock(®ister_mutex); +} + +/* + * /proc interface + */ + +#ifdef CONFIG_SND_PROC_FS + +static struct snd_info_entry *info_entry; + +static void +info_read(struct snd_info_entry *entry, struct snd_info_buffer *buf) +{ + mutex_lock(®ister_mutex); + snd_iprintf(buf, "OSS sequencer emulation version %s\n", SNDRV_SEQ_OSS_VERSION_STR); + snd_seq_oss_system_info_read(buf); + snd_seq_oss_synth_info_read(buf); + snd_seq_oss_midi_info_read(buf); + mutex_unlock(®ister_mutex); +} + + +static int __init +register_proc(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, SNDRV_SEQ_OSS_PROCNAME, snd_seq_root); + if (entry == NULL) + return -ENOMEM; + + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->private_data = NULL; + entry->c.text.read = info_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return -ENOMEM; + } + info_entry = entry; + return 0; +} + +static void +unregister_proc(void) +{ + snd_info_free_entry(info_entry); + info_entry = NULL; +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h new file mode 100644 index 0000000000..6c2c4fb9b7 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_device.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OSS compatible sequencer driver + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SEQ_OSS_DEVICE_H +#define __SEQ_OSS_DEVICE_H + +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> +#include <sound/core.h> +#include <sound/seq_oss.h> +#include <sound/rawmidi.h> +#include <sound/seq_kernel.h> +#include <sound/info.h> +#include "../seq_clientmgr.h" + +/* max. applications */ +#define SNDRV_SEQ_OSS_MAX_CLIENTS 16 +#define SNDRV_SEQ_OSS_MAX_SYNTH_DEVS 16 +#define SNDRV_SEQ_OSS_MAX_MIDI_DEVS 32 + +/* version */ +#define SNDRV_SEQ_OSS_MAJOR_VERSION 0 +#define SNDRV_SEQ_OSS_MINOR_VERSION 1 +#define SNDRV_SEQ_OSS_TINY_VERSION 8 +#define SNDRV_SEQ_OSS_VERSION_STR "0.1.8" + +/* device and proc interface name */ +#define SNDRV_SEQ_OSS_PROCNAME "oss" + + +/* + * type definitions + */ + +typedef unsigned int reltime_t; +typedef unsigned int abstime_t; + + +/* + * synthesizer channel information + */ +struct seq_oss_chinfo { + int note, vel; +}; + +/* + * synthesizer information + */ +struct seq_oss_synthinfo { + struct snd_seq_oss_arg arg; + struct seq_oss_chinfo *ch; + struct seq_oss_synth_sysex *sysex; + int nr_voices; + int opened; + int is_midi; + int midi_mapped; +}; + + +/* + * sequencer client information + */ + +struct seq_oss_devinfo { + + int index; /* application index */ + int cseq; /* sequencer client number */ + int port; /* sequencer port number */ + int queue; /* sequencer queue number */ + + struct snd_seq_addr addr; /* address of this device */ + + int seq_mode; /* sequencer mode */ + int file_mode; /* file access */ + + /* midi device table */ + int max_mididev; + + /* synth device table */ + int max_synthdev; + struct seq_oss_synthinfo synths[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS]; + int synth_opened; + + /* output queue */ + struct seq_oss_writeq *writeq; + + /* midi input queue */ + struct seq_oss_readq *readq; + + /* timer */ + struct seq_oss_timer *timer; +}; + + +/* + * function prototypes + */ + +/* create/delete OSS sequencer client */ +int snd_seq_oss_create_client(void); +int snd_seq_oss_delete_client(void); + +/* device file interface */ +int snd_seq_oss_open(struct file *file, int level); +void snd_seq_oss_release(struct seq_oss_devinfo *dp); +int snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long arg); +int snd_seq_oss_read(struct seq_oss_devinfo *dev, char __user *buf, int count); +int snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt); +__poll_t snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait); + +void snd_seq_oss_reset(struct seq_oss_devinfo *dp); + +/* */ +void snd_seq_oss_process_queue(struct seq_oss_devinfo *dp, abstime_t time); + + +/* proc interface */ +void snd_seq_oss_system_info_read(struct snd_info_buffer *buf); +void snd_seq_oss_midi_info_read(struct snd_info_buffer *buf); +void snd_seq_oss_synth_info_read(struct snd_info_buffer *buf); +void snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf); + +/* file mode macros */ +#define is_read_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_READ) +#define is_write_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_WRITE) +#define is_nonblock_mode(mode) ((mode) & SNDRV_SEQ_OSS_FILE_NONBLOCK) + +/* dispatch event */ +static inline int +snd_seq_oss_dispatch(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int atomic, int hop) +{ + return snd_seq_kernel_client_dispatch(dp->cseq, ev, atomic, hop); +} + +/* ioctl for writeq */ +static inline int +snd_seq_oss_control(struct seq_oss_devinfo *dp, unsigned int type, void *arg) +{ + int err; + + snd_seq_client_ioctl_lock(dp->cseq); + err = snd_seq_kernel_client_ctl(dp->cseq, type, arg); + snd_seq_client_ioctl_unlock(dp->cseq); + return err; +} + +/* fill the addresses in header */ +static inline void +snd_seq_oss_fill_addr(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, + int dest_client, int dest_port) +{ + ev->queue = dp->queue; + ev->source = dp->addr; + ev->dest.client = dest_client; + ev->dest.port = dest_port; +} + + +/* misc. functions for proc interface */ +char *enabled_str(int bool); + +#endif /* __SEQ_OSS_DEVICE_H */ diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c new file mode 100644 index 0000000000..7b7c925dd3 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_event.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_device.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include <sound/seq_oss_legacy.h> +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include <linux/nospec.h> + + +/* + * prototypes + */ +static int extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev); +static int chn_voice_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int chn_common_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int timing_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int local_event(struct seq_oss_devinfo *dp, union evrec *event_rec, struct snd_seq_event *ev); +static int old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev); +static int note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev); +static int note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev); +static int set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev); +static int set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev); +static int set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev); + + +/* + * convert an OSS event to ALSA event + * return 0 : enqueued + * non-zero : invalid - ignored + */ + +int +snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + switch (q->s.code) { + case SEQ_EXTENDED: + return extended_event(dp, q, ev); + + case EV_CHN_VOICE: + return chn_voice_event(dp, q, ev); + + case EV_CHN_COMMON: + return chn_common_event(dp, q, ev); + + case EV_TIMING: + return timing_event(dp, q, ev); + + case EV_SEQ_LOCAL: + return local_event(dp, q, ev); + + case EV_SYSEX: + return snd_seq_oss_synth_sysex(dp, q->x.dev, q->x.buf, ev); + + case SEQ_MIDIPUTC: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + /* put a midi byte */ + if (! is_write_mode(dp->file_mode)) + break; + if (snd_seq_oss_midi_open(dp, q->s.dev, SNDRV_SEQ_OSS_FILE_WRITE)) + break; + if (snd_seq_oss_midi_filemode(dp, q->s.dev) & SNDRV_SEQ_OSS_FILE_WRITE) + return snd_seq_oss_midi_putc(dp, q->s.dev, q->s.parm1, ev); + break; + + case SEQ_ECHO: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return set_echo_event(dp, q, ev); + + case SEQ_PRIVATE: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return snd_seq_oss_synth_raw_event(dp, q->c[1], q->c, ev); + + default: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return -EINVAL; + return old_event(dp, q, ev); + } + return -EINVAL; +} + +/* old type events: mode1 only */ +static int +old_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + switch (q->s.code) { + case SEQ_NOTEOFF: + return note_off_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev); + + case SEQ_NOTEON: + return note_on_event(dp, 0, q->n.chn, q->n.note, q->n.vel, ev); + + case SEQ_WAIT: + /* skip */ + break; + + case SEQ_PGMCHANGE: + return set_control_event(dp, 0, SNDRV_SEQ_EVENT_PGMCHANGE, + q->n.chn, 0, q->n.note, ev); + + case SEQ_SYNCTIMER: + return snd_seq_oss_timer_reset(dp->timer); + } + + return -EINVAL; +} + +/* 8bytes extended event: mode1 only */ +static int +extended_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + int val; + + switch (q->e.cmd) { + case SEQ_NOTEOFF: + return note_off_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev); + + case SEQ_NOTEON: + return note_on_event(dp, q->e.dev, q->e.chn, q->e.p1, q->e.p2, ev); + + case SEQ_PGMCHANGE: + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_PGMCHANGE, + q->e.chn, 0, q->e.p1, ev); + + case SEQ_AFTERTOUCH: + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CHANPRESS, + q->e.chn, 0, q->e.p1, ev); + + case SEQ_BALANCE: + /* convert -128:127 to 0:127 */ + val = (char)q->e.p1; + val = (val + 128) / 2; + return set_control_event(dp, q->e.dev, SNDRV_SEQ_EVENT_CONTROLLER, + q->e.chn, CTL_PAN, val, ev); + + case SEQ_CONTROLLER: + val = ((short)q->e.p3 << 8) | (short)q->e.p2; + switch (q->e.p1) { + case CTRL_PITCH_BENDER: /* SEQ1 V2 control */ + /* -0x2000:0x1fff */ + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_PITCHBEND, + q->e.chn, 0, val, ev); + case CTRL_PITCH_BENDER_RANGE: + /* conversion: 100/semitone -> 128/semitone */ + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_REGPARAM, + q->e.chn, 0, val*128/100, ev); + default: + return set_control_event(dp, q->e.dev, + SNDRV_SEQ_EVENT_CONTROL14, + q->e.chn, q->e.p1, val, ev); + } + + case SEQ_VOLMODE: + return snd_seq_oss_synth_raw_event(dp, q->e.dev, q->c, ev); + + } + return -EINVAL; +} + +/* channel voice events: mode1 and 2 */ +static int +chn_voice_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + if (q->v.chn >= 32) + return -EINVAL; + switch (q->v.cmd) { + case MIDI_NOTEON: + return note_on_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev); + + case MIDI_NOTEOFF: + return note_off_event(dp, q->v.dev, q->v.chn, q->v.note, q->v.parm, ev); + + case MIDI_KEY_PRESSURE: + return set_note_event(dp, q->v.dev, SNDRV_SEQ_EVENT_KEYPRESS, + q->v.chn, q->v.note, q->v.parm, ev); + + } + return -EINVAL; +} + +/* channel common events: mode1 and 2 */ +static int +chn_common_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + if (q->l.chn >= 32) + return -EINVAL; + switch (q->l.cmd) { + case MIDI_PGM_CHANGE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PGMCHANGE, + q->l.chn, 0, q->l.p1, ev); + + case MIDI_CTL_CHANGE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CONTROLLER, + q->l.chn, q->l.p1, q->l.val, ev); + + case MIDI_PITCH_BEND: + /* conversion: 0:0x3fff -> -0x2000:0x1fff */ + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_PITCHBEND, + q->l.chn, 0, q->l.val - 8192, ev); + + case MIDI_CHN_PRESSURE: + return set_control_event(dp, q->l.dev, SNDRV_SEQ_EVENT_CHANPRESS, + q->l.chn, 0, q->l.val, ev); + } + return -EINVAL; +} + +/* timer events: mode1 and mode2 */ +static int +timing_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + switch (q->t.cmd) { + case TMR_ECHO: + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + return set_echo_event(dp, q, ev); + else { + union evrec tmp; + memset(&tmp, 0, sizeof(tmp)); + /* XXX: only for little-endian! */ + tmp.echo = (q->t.time << 8) | SEQ_ECHO; + return set_echo_event(dp, &tmp, ev); + } + + case TMR_STOP: + if (dp->seq_mode) + return snd_seq_oss_timer_stop(dp->timer); + return 0; + + case TMR_CONTINUE: + if (dp->seq_mode) + return snd_seq_oss_timer_continue(dp->timer); + return 0; + + case TMR_TEMPO: + if (dp->seq_mode) + return snd_seq_oss_timer_tempo(dp->timer, q->t.time); + return 0; + } + + return -EINVAL; +} + +/* local events: mode1 and 2 */ +static int +local_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev) +{ + return -EINVAL; +} + +/* + * process note-on event for OSS synth + * three different modes are available: + * - SNDRV_SEQ_OSS_PROCESS_EVENTS (for one-voice per channel mode) + * Accept note 255 as volume change. + * - SNDRV_SEQ_OSS_PASS_EVENTS + * Pass all events to lowlevel driver anyway + * - SNDRV_SEQ_OSS_PROCESS_KEYPRESS (mostly for Emu8000) + * Use key-pressure if note >= 128 + */ +static int +note_on_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info) + return -ENXIO; + + switch (info->arg.event_passing) { + case SNDRV_SEQ_OSS_PROCESS_EVENTS: + if (! info->ch || ch < 0 || ch >= info->nr_voices) { + /* pass directly */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + + ch = array_index_nospec(ch, info->nr_voices); + if (note == 255 && info->ch[ch].note >= 0) { + /* volume control */ + int type; + //if (! vel) + /* set volume to zero -- note off */ + // type = SNDRV_SEQ_EVENT_NOTEOFF; + //else + if (info->ch[ch].vel) + /* sample already started -- volume change */ + type = SNDRV_SEQ_EVENT_KEYPRESS; + else + /* sample not started -- start now */ + type = SNDRV_SEQ_EVENT_NOTEON; + info->ch[ch].vel = vel; + return set_note_event(dp, dev, type, ch, info->ch[ch].note, vel, ev); + } else if (note >= 128) + return -EINVAL; /* invalid */ + + if (note != info->ch[ch].note && info->ch[ch].note >= 0) + /* note changed - note off at beginning */ + set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, info->ch[ch].note, 0, ev); + /* set current status */ + info->ch[ch].note = note; + info->ch[ch].vel = vel; + if (vel) /* non-zero velocity - start the note now */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + return -EINVAL; + + case SNDRV_SEQ_OSS_PASS_EVENTS: + /* pass the event anyway */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + + case SNDRV_SEQ_OSS_PROCESS_KEYPRESS: + if (note >= 128) /* key pressure: shifted by 128 */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_KEYPRESS, ch, note - 128, vel, ev); + else /* normal note-on event */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + return -EINVAL; +} + +/* + * process note-off event for OSS synth + */ +static int +note_off_event(struct seq_oss_devinfo *dp, int dev, int ch, int note, int vel, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info) + return -ENXIO; + + switch (info->arg.event_passing) { + case SNDRV_SEQ_OSS_PROCESS_EVENTS: + if (! info->ch || ch < 0 || ch >= info->nr_voices) { + /* pass directly */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEON, ch, note, vel, ev); + } + + ch = array_index_nospec(ch, info->nr_voices); + if (info->ch[ch].note >= 0) { + note = info->ch[ch].note; + info->ch[ch].vel = 0; + info->ch[ch].note = -1; + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev); + } + return -EINVAL; /* invalid */ + + case SNDRV_SEQ_OSS_PASS_EVENTS: + case SNDRV_SEQ_OSS_PROCESS_KEYPRESS: + /* pass the event anyway */ + return set_note_event(dp, dev, SNDRV_SEQ_EVENT_NOTEOFF, ch, note, vel, ev); + + } + return -EINVAL; +} + +/* + * create a note event + */ +static int +set_note_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int note, int vel, struct snd_seq_event *ev) +{ + if (!snd_seq_oss_synth_info(dp, dev)) + return -ENXIO; + + ev->type = type; + snd_seq_oss_synth_addr(dp, dev, ev); + ev->data.note.channel = ch; + ev->data.note.note = note; + ev->data.note.velocity = vel; + + return 0; +} + +/* + * create a control event + */ +static int +set_control_event(struct seq_oss_devinfo *dp, int dev, int type, int ch, int param, int val, struct snd_seq_event *ev) +{ + if (!snd_seq_oss_synth_info(dp, dev)) + return -ENXIO; + + ev->type = type; + snd_seq_oss_synth_addr(dp, dev, ev); + ev->data.control.channel = ch; + ev->data.control.param = param; + ev->data.control.value = val; + + return 0; +} + +/* + * create an echo event + */ +static int +set_echo_event(struct seq_oss_devinfo *dp, union evrec *rec, struct snd_seq_event *ev) +{ + ev->type = SNDRV_SEQ_EVENT_ECHO; + /* echo back to itself */ + snd_seq_oss_fill_addr(dp, ev, dp->addr.client, dp->addr.port); + memcpy(&ev->data, rec, LONG_EVENT_SIZE); + return 0; +} + +/* + * event input callback from ALSA sequencer: + * the echo event is processed here. + */ +int +snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data; + union evrec *rec; + + if (ev->type != SNDRV_SEQ_EVENT_ECHO) + return snd_seq_oss_midi_input(ev, direct, private_data); + + if (ev->source.client != dp->cseq) + return 0; /* ignored */ + + rec = (union evrec*)&ev->data; + if (rec->s.code == SEQ_SYNCTIMER) { + /* sync echo back */ + snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time); + + } else { + /* echo back event */ + if (dp->readq == NULL) + return 0; + snd_seq_oss_readq_put_event(dp->readq, rec); + } + return 0; +} + diff --git a/sound/core/seq/oss/seq_oss_event.h b/sound/core/seq/oss/seq_oss_event.h new file mode 100644 index 0000000000..b4f723949a --- /dev/null +++ b/sound/core/seq/oss/seq_oss_event.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OSS compatible sequencer driver + * + * seq_oss_event.h - OSS event queue record + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SEQ_OSS_EVENT_H +#define __SEQ_OSS_EVENT_H + +#include "seq_oss_device.h" + +#define SHORT_EVENT_SIZE 4 +#define LONG_EVENT_SIZE 8 + +/* short event (4bytes) */ +struct evrec_short { + unsigned char code; + unsigned char parm1; + unsigned char dev; + unsigned char parm2; +}; + +/* short note events (4bytes) */ +struct evrec_note { + unsigned char code; + unsigned char chn; + unsigned char note; + unsigned char vel; +}; + +/* long timer events (8bytes) */ +struct evrec_timer { + unsigned char code; + unsigned char cmd; + unsigned char dummy1, dummy2; + unsigned int time; +}; + +/* long extended events (8bytes) */ +struct evrec_extended { + unsigned char code; + unsigned char cmd; + unsigned char dev; + unsigned char chn; + unsigned char p1, p2, p3, p4; +}; + +/* long channel events (8bytes) */ +struct evrec_long { + unsigned char code; + unsigned char dev; + unsigned char cmd; + unsigned char chn; + unsigned char p1, p2; + unsigned short val; +}; + +/* channel voice events (8bytes) */ +struct evrec_voice { + unsigned char code; + unsigned char dev; + unsigned char cmd; + unsigned char chn; + unsigned char note, parm; + unsigned short dummy; +}; + +/* sysex events (8bytes) */ +struct evrec_sysex { + unsigned char code; + unsigned char dev; + unsigned char buf[6]; +}; + +/* event record */ +union evrec { + struct evrec_short s; + struct evrec_note n; + struct evrec_long l; + struct evrec_voice v; + struct evrec_timer t; + struct evrec_extended e; + struct evrec_sysex x; + unsigned int echo; + unsigned char c[LONG_EVENT_SIZE]; +}; + +#define ev_is_long(ev) ((ev)->s.code >= 128) +#define ev_length(ev) ((ev)->s.code >= 128 ? LONG_EVENT_SIZE : SHORT_EVENT_SIZE) + +int snd_seq_oss_process_event(struct seq_oss_devinfo *dp, union evrec *q, struct snd_seq_event *ev); +int snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *q); +int snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data, int atomic, int hop); + + +#endif /* __SEQ_OSS_EVENT_H */ diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c new file mode 100644 index 0000000000..42d4e7535a --- /dev/null +++ b/sound/core/seq/oss/seq_oss_init.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * open/close and reset interface + * + * Copyright (C) 1998-1999 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_device.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_writeq.h" +#include "seq_oss_readq.h" +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include <linux/init.h> +#include <linux/export.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +/* + * common variables + */ +static int maxqlen = SNDRV_SEQ_OSS_MAX_QLEN; +module_param(maxqlen, int, 0444); +MODULE_PARM_DESC(maxqlen, "maximum queue length"); + +static int system_client = -1; /* ALSA sequencer client number */ +static int system_port = -1; + +static int num_clients; +static struct seq_oss_devinfo *client_table[SNDRV_SEQ_OSS_MAX_CLIENTS]; + + +/* + * prototypes + */ +static int receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop); +static int translate_mode(struct file *file); +static int create_port(struct seq_oss_devinfo *dp); +static int delete_port(struct seq_oss_devinfo *dp); +static int alloc_seq_queue(struct seq_oss_devinfo *dp); +static int delete_seq_queue(int queue); +static void free_devinfo(void *private); + +#define call_ctl(type,rec) snd_seq_kernel_client_ctl(system_client, type, rec) + + +/* call snd_seq_oss_midi_lookup_ports() asynchronously */ +static void async_call_lookup_ports(struct work_struct *work) +{ + snd_seq_oss_midi_lookup_ports(system_client); +} + +static DECLARE_WORK(async_lookup_work, async_call_lookup_ports); + +/* + * create sequencer client for OSS sequencer + */ +int __init +snd_seq_oss_create_client(void) +{ + int rc; + struct snd_seq_port_info *port; + struct snd_seq_port_callback port_callback; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + rc = -ENOMEM; + goto __error; + } + + /* create ALSA client */ + rc = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_OSS, + "OSS sequencer"); + if (rc < 0) + goto __error; + + system_client = rc; + + /* create announcement receiver port */ + strcpy(port->name, "Receiver"); + port->addr.client = system_client; + port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* receive only */ + port->type = 0; + + memset(&port_callback, 0, sizeof(port_callback)); + /* don't set port_callback.owner here. otherwise the module counter + * is incremented and we can no longer release the module.. + */ + port_callback.event_input = receive_announce; + port->kernel = &port_callback; + + if (call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, port) >= 0) { + struct snd_seq_port_subscribe subs; + + system_port = port->addr.port; + memset(&subs, 0, sizeof(subs)); + subs.sender.client = SNDRV_SEQ_CLIENT_SYSTEM; + subs.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + subs.dest.client = system_client; + subs.dest.port = system_port; + call_ctl(SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs); + } + rc = 0; + + /* look up midi devices */ + schedule_work(&async_lookup_work); + + __error: + kfree(port); + return rc; +} + + +/* + * receive annoucement from system port, and check the midi device + */ +static int +receive_announce(struct snd_seq_event *ev, int direct, void *private, int atomic, int hop) +{ + struct snd_seq_port_info pinfo; + + if (atomic) + return 0; /* it must not happen */ + + switch (ev->type) { + case SNDRV_SEQ_EVENT_PORT_START: + case SNDRV_SEQ_EVENT_PORT_CHANGE: + if (ev->data.addr.client == system_client) + break; /* ignore myself */ + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.addr = ev->data.addr; + if (call_ctl(SNDRV_SEQ_IOCTL_GET_PORT_INFO, &pinfo) >= 0) + snd_seq_oss_midi_check_new_port(&pinfo); + break; + + case SNDRV_SEQ_EVENT_PORT_EXIT: + if (ev->data.addr.client == system_client) + break; /* ignore myself */ + snd_seq_oss_midi_check_exit_port(ev->data.addr.client, + ev->data.addr.port); + break; + } + return 0; +} + + +/* + * delete OSS sequencer client + */ +int +snd_seq_oss_delete_client(void) +{ + cancel_work_sync(&async_lookup_work); + if (system_client >= 0) + snd_seq_delete_kernel_client(system_client); + + snd_seq_oss_midi_clear_all(); + + return 0; +} + + +/* + * open sequencer device + */ +int +snd_seq_oss_open(struct file *file, int level) +{ + int i, rc; + struct seq_oss_devinfo *dp; + + dp = kzalloc(sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + dp->cseq = system_client; + dp->port = -1; + dp->queue = -1; + + for (i = 0; i < SNDRV_SEQ_OSS_MAX_CLIENTS; i++) { + if (client_table[i] == NULL) + break; + } + + dp->index = i; + if (i >= SNDRV_SEQ_OSS_MAX_CLIENTS) { + pr_debug("ALSA: seq_oss: too many applications\n"); + rc = -ENOMEM; + goto _error; + } + + /* look up synth and midi devices */ + snd_seq_oss_synth_setup(dp); + snd_seq_oss_midi_setup(dp); + + if (dp->synth_opened == 0 && dp->max_mididev == 0) { + /* pr_err("ALSA: seq_oss: no device found\n"); */ + rc = -ENODEV; + goto _error; + } + + /* create port */ + rc = create_port(dp); + if (rc < 0) { + pr_err("ALSA: seq_oss: can't create port\n"); + goto _error; + } + + /* allocate queue */ + rc = alloc_seq_queue(dp); + if (rc < 0) + goto _error; + + /* set address */ + dp->addr.client = dp->cseq; + dp->addr.port = dp->port; + /*dp->addr.queue = dp->queue;*/ + /*dp->addr.channel = 0;*/ + + dp->seq_mode = level; + + /* set up file mode */ + dp->file_mode = translate_mode(file); + + /* initialize read queue */ + if (is_read_mode(dp->file_mode)) { + dp->readq = snd_seq_oss_readq_new(dp, maxqlen); + if (!dp->readq) { + rc = -ENOMEM; + goto _error; + } + } + + /* initialize write queue */ + if (is_write_mode(dp->file_mode)) { + dp->writeq = snd_seq_oss_writeq_new(dp, maxqlen); + if (!dp->writeq) { + rc = -ENOMEM; + goto _error; + } + } + + /* initialize timer */ + dp->timer = snd_seq_oss_timer_new(dp); + if (!dp->timer) { + pr_err("ALSA: seq_oss: can't alloc timer\n"); + rc = -ENOMEM; + goto _error; + } + + /* set private data pointer */ + file->private_data = dp; + + /* set up for mode2 */ + if (level == SNDRV_SEQ_OSS_MODE_MUSIC) + snd_seq_oss_synth_setup_midi(dp); + else if (is_read_mode(dp->file_mode)) + snd_seq_oss_midi_open_all(dp, SNDRV_SEQ_OSS_FILE_READ); + + client_table[dp->index] = dp; + num_clients++; + + return 0; + + _error: + snd_seq_oss_synth_cleanup(dp); + snd_seq_oss_midi_cleanup(dp); + delete_seq_queue(dp->queue); + delete_port(dp); + + return rc; +} + +/* + * translate file flags to private mode + */ +static int +translate_mode(struct file *file) +{ + int file_mode = 0; + if ((file->f_flags & O_ACCMODE) != O_RDONLY) + file_mode |= SNDRV_SEQ_OSS_FILE_WRITE; + if ((file->f_flags & O_ACCMODE) != O_WRONLY) + file_mode |= SNDRV_SEQ_OSS_FILE_READ; + if (file->f_flags & O_NONBLOCK) + file_mode |= SNDRV_SEQ_OSS_FILE_NONBLOCK; + return file_mode; +} + + +/* + * create sequencer port + */ +static int +create_port(struct seq_oss_devinfo *dp) +{ + int rc; + struct snd_seq_port_info port; + struct snd_seq_port_callback callback; + + memset(&port, 0, sizeof(port)); + port.addr.client = dp->cseq; + sprintf(port.name, "Sequencer-%d", dp->index); + port.capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_WRITE; /* no subscription */ + port.type = SNDRV_SEQ_PORT_TYPE_SPECIFIC; + port.midi_channels = 128; + port.synth_voices = 128; + + memset(&callback, 0, sizeof(callback)); + callback.owner = THIS_MODULE; + callback.private_data = dp; + callback.event_input = snd_seq_oss_event_input; + callback.private_free = free_devinfo; + port.kernel = &callback; + + rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_PORT, &port); + if (rc < 0) + return rc; + + dp->port = port.addr.port; + + return 0; +} + +/* + * delete ALSA port + */ +static int +delete_port(struct seq_oss_devinfo *dp) +{ + if (dp->port < 0) { + kfree(dp); + return 0; + } + + return snd_seq_event_port_detach(dp->cseq, dp->port); +} + +/* + * allocate a queue + */ +static int +alloc_seq_queue(struct seq_oss_devinfo *dp) +{ + struct snd_seq_queue_info qinfo; + int rc; + + memset(&qinfo, 0, sizeof(qinfo)); + qinfo.owner = system_client; + qinfo.locked = 1; + strcpy(qinfo.name, "OSS Sequencer Emulation"); + rc = call_ctl(SNDRV_SEQ_IOCTL_CREATE_QUEUE, &qinfo); + if (rc < 0) + return rc; + dp->queue = qinfo.queue; + return 0; +} + +/* + * release queue + */ +static int +delete_seq_queue(int queue) +{ + struct snd_seq_queue_info qinfo; + int rc; + + if (queue < 0) + return 0; + memset(&qinfo, 0, sizeof(qinfo)); + qinfo.queue = queue; + rc = call_ctl(SNDRV_SEQ_IOCTL_DELETE_QUEUE, &qinfo); + if (rc < 0) + pr_err("ALSA: seq_oss: unable to delete queue %d (%d)\n", queue, rc); + return rc; +} + + +/* + * free device informations - private_free callback of port + */ +static void +free_devinfo(void *private) +{ + struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private; + + snd_seq_oss_timer_delete(dp->timer); + + snd_seq_oss_writeq_delete(dp->writeq); + + snd_seq_oss_readq_delete(dp->readq); + + kfree(dp); +} + + +/* + * close sequencer device + */ +void +snd_seq_oss_release(struct seq_oss_devinfo *dp) +{ + int queue; + + client_table[dp->index] = NULL; + num_clients--; + + snd_seq_oss_reset(dp); + + snd_seq_oss_synth_cleanup(dp); + snd_seq_oss_midi_cleanup(dp); + + /* clear slot */ + queue = dp->queue; + if (dp->port >= 0) + delete_port(dp); + delete_seq_queue(queue); +} + + +/* + * reset sequencer devices + */ +void +snd_seq_oss_reset(struct seq_oss_devinfo *dp) +{ + int i; + + /* reset all synth devices */ + for (i = 0; i < dp->max_synthdev; i++) + snd_seq_oss_synth_reset(dp, i); + + /* reset all midi devices */ + if (dp->seq_mode != SNDRV_SEQ_OSS_MODE_MUSIC) { + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_reset(dp, i); + } + + /* remove queues */ + if (dp->readq) + snd_seq_oss_readq_clear(dp->readq); + if (dp->writeq) + snd_seq_oss_writeq_clear(dp->writeq); + + /* reset timer */ + snd_seq_oss_timer_stop(dp->timer); +} + +#ifdef CONFIG_SND_PROC_FS +/* + * misc. functions for proc interface + */ +char * +enabled_str(int bool) +{ + return bool ? "enabled" : "disabled"; +} + +static const char * +filemode_str(int val) +{ + static const char * const str[] = { + "none", "read", "write", "read/write", + }; + return str[val & SNDRV_SEQ_OSS_FILE_ACMODE]; +} + + +/* + * proc interface + */ +void +snd_seq_oss_system_info_read(struct snd_info_buffer *buf) +{ + int i; + struct seq_oss_devinfo *dp; + + snd_iprintf(buf, "ALSA client number %d\n", system_client); + snd_iprintf(buf, "ALSA receiver port %d\n", system_port); + + snd_iprintf(buf, "\nNumber of applications: %d\n", num_clients); + for (i = 0; i < num_clients; i++) { + snd_iprintf(buf, "\nApplication %d: ", i); + dp = client_table[i]; + if (!dp) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "port %d : queue %d\n", dp->port, dp->queue); + snd_iprintf(buf, " sequencer mode = %s : file open mode = %s\n", + (dp->seq_mode ? "music" : "synth"), + filemode_str(dp->file_mode)); + if (dp->seq_mode) + snd_iprintf(buf, " timer tempo = %d, timebase = %d\n", + dp->timer->oss_tempo, dp->timer->oss_timebase); + snd_iprintf(buf, " max queue length %d\n", maxqlen); + if (is_read_mode(dp->file_mode) && dp->readq) + snd_seq_oss_readq_info_read(dp->readq, buf); + } +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_ioctl.c b/sound/core/seq/oss/seq_oss_ioctl.c new file mode 100644 index 0000000000..ccf682689e --- /dev/null +++ b/sound/core/seq/oss/seq_oss_ioctl.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * OSS compatible i/o control + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_device.h" +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include "seq_oss_timer.h" +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "seq_oss_event.h" + +static int snd_seq_oss_synth_info_user(struct seq_oss_devinfo *dp, void __user *arg) +{ + struct synth_info info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (snd_seq_oss_synth_make_info(dp, info.device, &info) < 0) + return -EINVAL; + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_seq_oss_midi_info_user(struct seq_oss_devinfo *dp, void __user *arg) +{ + struct midi_info info; + + if (copy_from_user(&info, arg, sizeof(info))) + return -EFAULT; + if (snd_seq_oss_midi_make_info(dp, info.device, &info) < 0) + return -EINVAL; + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; +} + +static int snd_seq_oss_oob_user(struct seq_oss_devinfo *dp, void __user *arg) +{ + unsigned char ev[8]; + struct snd_seq_event tmpev; + + if (copy_from_user(ev, arg, 8)) + return -EFAULT; + memset(&tmpev, 0, sizeof(tmpev)); + snd_seq_oss_fill_addr(dp, &tmpev, dp->addr.client, dp->addr.port); + tmpev.time.tick = 0; + if (! snd_seq_oss_process_event(dp, (union evrec *)ev, &tmpev)) { + snd_seq_oss_dispatch(dp, &tmpev, 0, 0); + } + return 0; +} + +int +snd_seq_oss_ioctl(struct seq_oss_devinfo *dp, unsigned int cmd, unsigned long carg) +{ + int dev, val; + void __user *arg = (void __user *)carg; + int __user *p = arg; + + switch (cmd) { + case SNDCTL_TMR_TIMEBASE: + case SNDCTL_TMR_TEMPO: + case SNDCTL_TMR_START: + case SNDCTL_TMR_STOP: + case SNDCTL_TMR_CONTINUE: + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SOURCE: + case SNDCTL_TMR_SELECT: + case SNDCTL_SEQ_CTRLRATE: + return snd_seq_oss_timer_ioctl(dp->timer, cmd, arg); + + case SNDCTL_SEQ_PANIC: + snd_seq_oss_reset(dp); + return -EINVAL; + + case SNDCTL_SEQ_SYNC: + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return 0; + while (snd_seq_oss_writeq_sync(dp->writeq)) + ; + if (signal_pending(current)) + return -ERESTARTSYS; + return 0; + + case SNDCTL_SEQ_RESET: + snd_seq_oss_reset(dp); + return 0; + + case SNDCTL_SEQ_TESTMIDI: + if (get_user(dev, p)) + return -EFAULT; + return snd_seq_oss_midi_open(dp, dev, dp->file_mode); + + case SNDCTL_SEQ_GETINCOUNT: + if (dp->readq == NULL || ! is_read_mode(dp->file_mode)) + return 0; + return put_user(dp->readq->qlen, p) ? -EFAULT : 0; + + case SNDCTL_SEQ_GETOUTCOUNT: + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return 0; + return put_user(snd_seq_oss_writeq_get_free_size(dp->writeq), p) ? -EFAULT : 0; + + case SNDCTL_SEQ_GETTIME: + return put_user(snd_seq_oss_timer_cur_tick(dp->timer), p) ? -EFAULT : 0; + + case SNDCTL_SEQ_RESETSAMPLES: + if (get_user(dev, p)) + return -EFAULT; + return snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + + case SNDCTL_SEQ_NRSYNTHS: + return put_user(dp->max_synthdev, p) ? -EFAULT : 0; + + case SNDCTL_SEQ_NRMIDIS: + return put_user(dp->max_mididev, p) ? -EFAULT : 0; + + case SNDCTL_SYNTH_MEMAVL: + if (get_user(dev, p)) + return -EFAULT; + val = snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + return put_user(val, p) ? -EFAULT : 0; + + case SNDCTL_FM_4OP_ENABLE: + if (get_user(dev, p)) + return -EFAULT; + snd_seq_oss_synth_ioctl(dp, dev, cmd, carg); + return 0; + + case SNDCTL_SYNTH_INFO: + case SNDCTL_SYNTH_ID: + return snd_seq_oss_synth_info_user(dp, arg); + + case SNDCTL_SEQ_OUTOFBAND: + return snd_seq_oss_oob_user(dp, arg); + + case SNDCTL_MIDI_INFO: + return snd_seq_oss_midi_info_user(dp, arg); + + case SNDCTL_SEQ_THRESHOLD: + if (! is_write_mode(dp->file_mode)) + return 0; + if (get_user(val, p)) + return -EFAULT; + if (val < 1) + val = 1; + if (val >= dp->writeq->maxlen) + val = dp->writeq->maxlen - 1; + snd_seq_oss_writeq_set_output(dp->writeq, val); + return 0; + + case SNDCTL_MIDI_PRETIME: + if (dp->readq == NULL || !is_read_mode(dp->file_mode)) + return 0; + if (get_user(val, p)) + return -EFAULT; + if (val <= 0) + val = -1; + else + val = (HZ * val) / 10; + dp->readq->pre_event_timeout = val; + return put_user(val, p) ? -EFAULT : 0; + + default: + if (! is_write_mode(dp->file_mode)) + return -EIO; + return snd_seq_oss_synth_ioctl(dp, 0, cmd, carg); + } + return 0; +} + diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c new file mode 100644 index 0000000000..f2940b2959 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_midi.c @@ -0,0 +1,718 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * MIDI device handlers + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include <sound/asoundef.h> +#include "seq_oss_midi.h" +#include "seq_oss_readq.h" +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include <sound/seq_midi_event.h> +#include "../seq_lock.h" +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/nospec.h> + + +/* + * constants + */ +#define SNDRV_SEQ_OSS_MAX_MIDI_NAME 30 + +/* + * definition of midi device record + */ +struct seq_oss_midi { + int seq_device; /* device number */ + int client; /* sequencer client number */ + int port; /* sequencer port number */ + unsigned int flags; /* port capability */ + int opened; /* flag for opening */ + unsigned char name[SNDRV_SEQ_OSS_MAX_MIDI_NAME]; + struct snd_midi_event *coder; /* MIDI event coder */ + struct seq_oss_devinfo *devinfo; /* assigned OSSseq device */ + snd_use_lock_t use_lock; + struct mutex open_mutex; +}; + + +/* + * midi device table + */ +static int max_midi_devs; +static struct seq_oss_midi *midi_devs[SNDRV_SEQ_OSS_MAX_MIDI_DEVS]; + +static DEFINE_SPINLOCK(register_lock); + +/* + * prototypes + */ +static struct seq_oss_midi *get_mdev(int dev); +static struct seq_oss_midi *get_mididev(struct seq_oss_devinfo *dp, int dev); +static int send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev); +static int send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev); + +/* + * look up the existing ports + * this looks a very exhausting job. + */ +int +snd_seq_oss_midi_lookup_ports(int client) +{ + struct snd_seq_client_info *clinfo; + struct snd_seq_port_info *pinfo; + + clinfo = kzalloc(sizeof(*clinfo), GFP_KERNEL); + pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); + if (! clinfo || ! pinfo) { + kfree(clinfo); + kfree(pinfo); + return -ENOMEM; + } + clinfo->client = -1; + while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, clinfo) == 0) { + if (clinfo->client == client) + continue; /* ignore myself */ + pinfo->addr.client = clinfo->client; + pinfo->addr.port = -1; + while (snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, pinfo) == 0) + snd_seq_oss_midi_check_new_port(pinfo); + } + kfree(clinfo); + kfree(pinfo); + return 0; +} + + +/* + */ +static struct seq_oss_midi * +get_mdev(int dev) +{ + struct seq_oss_midi *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + mdev = midi_devs[dev]; + if (mdev) + snd_use_lock_use(&mdev->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return mdev; +} + +/* + * look for the identical slot + */ +static struct seq_oss_midi * +find_slot(int client, int port) +{ + int i; + struct seq_oss_midi *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + mdev = midi_devs[i]; + if (mdev && mdev->client == client && mdev->port == port) { + /* found! */ + snd_use_lock_use(&mdev->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return mdev; + } + } + spin_unlock_irqrestore(®ister_lock, flags); + return NULL; +} + + +#define PERM_WRITE (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_SUBS_WRITE) +#define PERM_READ (SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ) +/* + * register a new port if it doesn't exist yet + */ +int +snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo) +{ + int i; + struct seq_oss_midi *mdev; + unsigned long flags; + + /* the port must include generic midi */ + if (! (pinfo->type & SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC)) + return 0; + /* either read or write subscribable */ + if ((pinfo->capability & PERM_WRITE) != PERM_WRITE && + (pinfo->capability & PERM_READ) != PERM_READ) + return 0; + + /* + * look for the identical slot + */ + mdev = find_slot(pinfo->addr.client, pinfo->addr.port); + if (mdev) { + /* already exists */ + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + /* + * allocate midi info record + */ + mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + /* copy the port information */ + mdev->client = pinfo->addr.client; + mdev->port = pinfo->addr.port; + mdev->flags = pinfo->capability; + mdev->opened = 0; + snd_use_lock_init(&mdev->use_lock); + mutex_init(&mdev->open_mutex); + + /* copy and truncate the name of synth device */ + strscpy(mdev->name, pinfo->name, sizeof(mdev->name)); + + /* create MIDI coder */ + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &mdev->coder) < 0) { + pr_err("ALSA: seq_oss: can't malloc midi coder\n"); + kfree(mdev); + return -ENOMEM; + } + /* OSS sequencer adds running status to all sequences */ + snd_midi_event_no_status(mdev->coder, 1); + + /* + * look for en empty slot + */ + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + if (midi_devs[i] == NULL) + break; + } + if (i >= max_midi_devs) { + if (max_midi_devs >= SNDRV_SEQ_OSS_MAX_MIDI_DEVS) { + spin_unlock_irqrestore(®ister_lock, flags); + snd_midi_event_free(mdev->coder); + kfree(mdev); + return -ENOMEM; + } + max_midi_devs++; + } + mdev->seq_device = i; + midi_devs[mdev->seq_device] = mdev; + spin_unlock_irqrestore(®ister_lock, flags); + + return 0; +} + +/* + * release the midi device if it was registered + */ +int +snd_seq_oss_midi_check_exit_port(int client, int port) +{ + struct seq_oss_midi *mdev; + unsigned long flags; + int index; + + mdev = find_slot(client, port); + if (mdev) { + spin_lock_irqsave(®ister_lock, flags); + midi_devs[mdev->seq_device] = NULL; + spin_unlock_irqrestore(®ister_lock, flags); + snd_use_lock_free(&mdev->use_lock); + snd_use_lock_sync(&mdev->use_lock); + snd_midi_event_free(mdev->coder); + kfree(mdev); + } + spin_lock_irqsave(®ister_lock, flags); + for (index = max_midi_devs - 1; index >= 0; index--) { + if (midi_devs[index]) + break; + } + max_midi_devs = index + 1; + spin_unlock_irqrestore(®ister_lock, flags); + return 0; +} + + +/* + * release the midi device if it was registered + */ +void +snd_seq_oss_midi_clear_all(void) +{ + int i; + struct seq_oss_midi *mdev; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_midi_devs; i++) { + mdev = midi_devs[i]; + if (mdev) { + snd_midi_event_free(mdev->coder); + kfree(mdev); + midi_devs[i] = NULL; + } + } + max_midi_devs = 0; + spin_unlock_irqrestore(®ister_lock, flags); +} + + +/* + * set up midi tables + */ +void +snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp) +{ + spin_lock_irq(®ister_lock); + dp->max_mididev = max_midi_devs; + spin_unlock_irq(®ister_lock); +} + +/* + * clean up midi tables + */ +void +snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp) +{ + int i; + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_close(dp, i); + dp->max_mididev = 0; +} + + +/* + * open all midi devices. ignore errors. + */ +void +snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode) +{ + int i; + for (i = 0; i < dp->max_mididev; i++) + snd_seq_oss_midi_open(dp, i, file_mode); +} + + +/* + * get the midi device information + */ +static struct seq_oss_midi * +get_mididev(struct seq_oss_devinfo *dp, int dev) +{ + if (dev < 0 || dev >= dp->max_mididev) + return NULL; + dev = array_index_nospec(dev, dp->max_mididev); + return get_mdev(dev); +} + + +/* + * open the midi device if not opened yet + */ +int +snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int fmode) +{ + int perm; + struct seq_oss_midi *mdev; + struct snd_seq_port_subscribe subs; + int err; + + mdev = get_mididev(dp, dev); + if (!mdev) + return -ENODEV; + + mutex_lock(&mdev->open_mutex); + /* already used? */ + if (mdev->opened && mdev->devinfo != dp) { + err = -EBUSY; + goto unlock; + } + + perm = 0; + if (is_write_mode(fmode)) + perm |= PERM_WRITE; + if (is_read_mode(fmode)) + perm |= PERM_READ; + perm &= mdev->flags; + if (perm == 0) { + err = -ENXIO; + goto unlock; + } + + /* already opened? */ + if ((mdev->opened & perm) == perm) { + err = 0; + goto unlock; + } + + perm &= ~mdev->opened; + + memset(&subs, 0, sizeof(subs)); + + if (perm & PERM_WRITE) { + subs.sender = dp->addr; + subs.dest.client = mdev->client; + subs.dest.port = mdev->port; + if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0) + mdev->opened |= PERM_WRITE; + } + if (perm & PERM_READ) { + subs.sender.client = mdev->client; + subs.sender.port = mdev->port; + subs.dest = dp->addr; + subs.flags = SNDRV_SEQ_PORT_SUBS_TIMESTAMP; + subs.queue = dp->queue; /* queue for timestamps */ + if (snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &subs) >= 0) + mdev->opened |= PERM_READ; + } + + if (! mdev->opened) { + err = -ENXIO; + goto unlock; + } + + mdev->devinfo = dp; + err = 0; + + unlock: + mutex_unlock(&mdev->open_mutex); + snd_use_lock_free(&mdev->use_lock); + return err; +} + +/* + * close the midi device if already opened + */ +int +snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_midi *mdev; + struct snd_seq_port_subscribe subs; + + mdev = get_mididev(dp, dev); + if (!mdev) + return -ENODEV; + mutex_lock(&mdev->open_mutex); + if (!mdev->opened || mdev->devinfo != dp) + goto unlock; + + memset(&subs, 0, sizeof(subs)); + if (mdev->opened & PERM_WRITE) { + subs.sender = dp->addr; + subs.dest.client = mdev->client; + subs.dest.port = mdev->port; + snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs); + } + if (mdev->opened & PERM_READ) { + subs.sender.client = mdev->client; + subs.sender.port = mdev->port; + subs.dest = dp->addr; + snd_seq_kernel_client_ctl(dp->cseq, SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, &subs); + } + + mdev->opened = 0; + mdev->devinfo = NULL; + + unlock: + mutex_unlock(&mdev->open_mutex); + snd_use_lock_free(&mdev->use_lock); + return 0; +} + +/* + * change seq capability flags to file mode flags + */ +int +snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_midi *mdev; + int mode; + + mdev = get_mididev(dp, dev); + if (!mdev) + return 0; + + mode = 0; + if (mdev->opened & PERM_WRITE) + mode |= SNDRV_SEQ_OSS_FILE_WRITE; + if (mdev->opened & PERM_READ) + mode |= SNDRV_SEQ_OSS_FILE_READ; + + snd_use_lock_free(&mdev->use_lock); + return mode; +} + +/* + * reset the midi device and close it: + * so far, only close the device. + */ +void +snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_midi *mdev; + + mdev = get_mididev(dp, dev); + if (!mdev) + return; + if (! mdev->opened) { + snd_use_lock_free(&mdev->use_lock); + return; + } + + if (mdev->opened & PERM_WRITE) { + struct snd_seq_event ev; + int c; + + memset(&ev, 0, sizeof(ev)); + ev.dest.client = mdev->client; + ev.dest.port = mdev->port; + ev.queue = dp->queue; + ev.source.port = dp->port; + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) { + ev.type = SNDRV_SEQ_EVENT_SENSING; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + } + for (c = 0; c < 16; c++) { + ev.type = SNDRV_SEQ_EVENT_CONTROLLER; + ev.data.control.channel = c; + ev.data.control.param = MIDI_CTL_ALL_NOTES_OFF; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + ev.data.control.param = + MIDI_CTL_RESET_CONTROLLERS; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + ev.type = SNDRV_SEQ_EVENT_PITCHBEND; + ev.data.control.value = 0; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + } + } + } + // snd_seq_oss_midi_close(dp, dev); + snd_use_lock_free(&mdev->use_lock); +} + + +/* + * get client/port of the specified MIDI device + */ +void +snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr) +{ + struct seq_oss_midi *mdev; + + mdev = get_mididev(dp, dev); + if (!mdev) + return; + addr->client = mdev->client; + addr->port = mdev->port; + snd_use_lock_free(&mdev->use_lock); +} + + +/* + * input callback - this can be atomic + */ +int +snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private_data) +{ + struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data; + struct seq_oss_midi *mdev; + int rc; + + if (dp->readq == NULL) + return 0; + mdev = find_slot(ev->source.client, ev->source.port); + if (!mdev) + return 0; + if (! (mdev->opened & PERM_READ)) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } + + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) + rc = send_synth_event(dp, ev, mdev->seq_device); + else + rc = send_midi_event(dp, ev, mdev); + + snd_use_lock_free(&mdev->use_lock); + return rc; +} + +/* + * convert ALSA sequencer event to OSS synth event + */ +static int +send_synth_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, int dev) +{ + union evrec ossev; + + memset(&ossev, 0, sizeof(ossev)); + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + ossev.v.cmd = MIDI_NOTEON; break; + case SNDRV_SEQ_EVENT_NOTEOFF: + ossev.v.cmd = MIDI_NOTEOFF; break; + case SNDRV_SEQ_EVENT_KEYPRESS: + ossev.v.cmd = MIDI_KEY_PRESSURE; break; + case SNDRV_SEQ_EVENT_CONTROLLER: + ossev.l.cmd = MIDI_CTL_CHANGE; break; + case SNDRV_SEQ_EVENT_PGMCHANGE: + ossev.l.cmd = MIDI_PGM_CHANGE; break; + case SNDRV_SEQ_EVENT_CHANPRESS: + ossev.l.cmd = MIDI_CHN_PRESSURE; break; + case SNDRV_SEQ_EVENT_PITCHBEND: + ossev.l.cmd = MIDI_PITCH_BEND; break; + default: + return 0; /* not supported */ + } + + ossev.v.dev = dev; + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + case SNDRV_SEQ_EVENT_NOTEOFF: + case SNDRV_SEQ_EVENT_KEYPRESS: + ossev.v.code = EV_CHN_VOICE; + ossev.v.note = ev->data.note.note; + ossev.v.parm = ev->data.note.velocity; + ossev.v.chn = ev->data.note.channel; + break; + case SNDRV_SEQ_EVENT_CONTROLLER: + case SNDRV_SEQ_EVENT_PGMCHANGE: + case SNDRV_SEQ_EVENT_CHANPRESS: + ossev.l.code = EV_CHN_COMMON; + ossev.l.p1 = ev->data.control.param; + ossev.l.val = ev->data.control.value; + ossev.l.chn = ev->data.control.channel; + break; + case SNDRV_SEQ_EVENT_PITCHBEND: + ossev.l.code = EV_CHN_COMMON; + ossev.l.val = ev->data.control.value + 8192; + ossev.l.chn = ev->data.control.channel; + break; + } + + snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode); + snd_seq_oss_readq_put_event(dp->readq, &ossev); + + return 0; +} + +/* + * decode event and send MIDI bytes to read queue + */ +static int +send_midi_event(struct seq_oss_devinfo *dp, struct snd_seq_event *ev, struct seq_oss_midi *mdev) +{ + char msg[32]; + int len; + + snd_seq_oss_readq_put_timestamp(dp->readq, ev->time.tick, dp->seq_mode); + if (!dp->timer->running) + len = snd_seq_oss_timer_start(dp->timer); + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { + snd_seq_oss_readq_sysex(dp->readq, mdev->seq_device, ev); + snd_midi_event_reset_decode(mdev->coder); + } else { + len = snd_midi_event_decode(mdev->coder, msg, sizeof(msg), ev); + if (len > 0) + snd_seq_oss_readq_puts(dp->readq, mdev->seq_device, msg, len); + } + + return 0; +} + + +/* + * dump midi data + * return 0 : enqueued + * non-zero : invalid - ignored + */ +int +snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c, struct snd_seq_event *ev) +{ + struct seq_oss_midi *mdev; + + mdev = get_mididev(dp, dev); + if (!mdev) + return -ENODEV; + if (snd_midi_event_encode_byte(mdev->coder, c, ev)) { + snd_seq_oss_fill_addr(dp, ev, mdev->client, mdev->port); + snd_use_lock_free(&mdev->use_lock); + return 0; + } + snd_use_lock_free(&mdev->use_lock); + return -EINVAL; +} + +/* + * create OSS compatible midi_info record + */ +int +snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf) +{ + struct seq_oss_midi *mdev; + + mdev = get_mididev(dp, dev); + if (!mdev) + return -ENXIO; + inf->device = dev; + inf->dev_type = 0; /* FIXME: ?? */ + inf->capabilities = 0; /* FIXME: ?? */ + strscpy(inf->name, mdev->name, sizeof(inf->name)); + snd_use_lock_free(&mdev->use_lock); + return 0; +} + + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ +static char * +capmode_str(int val) +{ + val &= PERM_READ|PERM_WRITE; + if (val == (PERM_READ|PERM_WRITE)) + return "read/write"; + else if (val == PERM_READ) + return "read"; + else if (val == PERM_WRITE) + return "write"; + else + return "none"; +} + +void +snd_seq_oss_midi_info_read(struct snd_info_buffer *buf) +{ + int i; + struct seq_oss_midi *mdev; + + snd_iprintf(buf, "\nNumber of MIDI devices: %d\n", max_midi_devs); + for (i = 0; i < max_midi_devs; i++) { + snd_iprintf(buf, "\nmidi %d: ", i); + mdev = get_mdev(i); + if (mdev == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "[%s] ALSA port %d:%d\n", mdev->name, + mdev->client, mdev->port); + snd_iprintf(buf, " capability %s / opened %s\n", + capmode_str(mdev->flags), + capmode_str(mdev->opened)); + snd_use_lock_free(&mdev->use_lock); + } +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_midi.h b/sound/core/seq/oss/seq_oss_midi.h new file mode 100644 index 0000000000..bcc1683773 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_midi.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OSS compatible sequencer driver + * + * midi device information + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SEQ_OSS_MIDI_H +#define __SEQ_OSS_MIDI_H + +#include "seq_oss_device.h" +#include <sound/seq_oss_legacy.h> + +int snd_seq_oss_midi_lookup_ports(int client); +int snd_seq_oss_midi_check_new_port(struct snd_seq_port_info *pinfo); +int snd_seq_oss_midi_check_exit_port(int client, int port); +void snd_seq_oss_midi_clear_all(void); + +void snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp); +void snd_seq_oss_midi_cleanup(struct seq_oss_devinfo *dp); + +int snd_seq_oss_midi_open(struct seq_oss_devinfo *dp, int dev, int file_mode); +void snd_seq_oss_midi_open_all(struct seq_oss_devinfo *dp, int file_mode); +int snd_seq_oss_midi_close(struct seq_oss_devinfo *dp, int dev); +void snd_seq_oss_midi_reset(struct seq_oss_devinfo *dp, int dev); +int snd_seq_oss_midi_putc(struct seq_oss_devinfo *dp, int dev, unsigned char c, + struct snd_seq_event *ev); +int snd_seq_oss_midi_input(struct snd_seq_event *ev, int direct, void *private); +int snd_seq_oss_midi_filemode(struct seq_oss_devinfo *dp, int dev); +int snd_seq_oss_midi_make_info(struct seq_oss_devinfo *dp, int dev, struct midi_info *inf); +void snd_seq_oss_midi_get_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_addr *addr); + +#endif diff --git a/sound/core/seq/oss/seq_oss_readq.c b/sound/core/seq/oss/seq_oss_readq.c new file mode 100644 index 0000000000..f0db5d3dcb --- /dev/null +++ b/sound/core/seq/oss/seq_oss_readq.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * seq_oss_readq.c - MIDI input queue + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_readq.h" +#include "seq_oss_event.h" +#include <sound/seq_oss_legacy.h> +#include "../seq_lock.h" +#include <linux/wait.h> +#include <linux/slab.h> + +/* + * constants + */ +//#define SNDRV_SEQ_OSS_MAX_TIMEOUT (unsigned long)(-1) +#define SNDRV_SEQ_OSS_MAX_TIMEOUT (HZ * 3600) + + +/* + * prototypes + */ + + +/* + * create a read queue + */ +struct seq_oss_readq * +snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen) +{ + struct seq_oss_readq *q; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + + q->q = kcalloc(maxlen, sizeof(union evrec), GFP_KERNEL); + if (!q->q) { + kfree(q); + return NULL; + } + + q->maxlen = maxlen; + q->qlen = 0; + q->head = q->tail = 0; + init_waitqueue_head(&q->midi_sleep); + spin_lock_init(&q->lock); + q->pre_event_timeout = SNDRV_SEQ_OSS_MAX_TIMEOUT; + q->input_time = (unsigned long)-1; + + return q; +} + +/* + * delete the read queue + */ +void +snd_seq_oss_readq_delete(struct seq_oss_readq *q) +{ + if (q) { + kfree(q->q); + kfree(q); + } +} + +/* + * reset the read queue + */ +void +snd_seq_oss_readq_clear(struct seq_oss_readq *q) +{ + if (q->qlen) { + q->qlen = 0; + q->head = q->tail = 0; + } + /* if someone sleeping, wake'em up */ + wake_up(&q->midi_sleep); + q->input_time = (unsigned long)-1; +} + +/* + * put a midi byte + */ +int +snd_seq_oss_readq_puts(struct seq_oss_readq *q, int dev, unsigned char *data, int len) +{ + union evrec rec; + int result; + + memset(&rec, 0, sizeof(rec)); + rec.c[0] = SEQ_MIDIPUTC; + rec.c[2] = dev; + + while (len-- > 0) { + rec.c[1] = *data++; + result = snd_seq_oss_readq_put_event(q, &rec); + if (result < 0) + return result; + } + return 0; +} + +/* + * put MIDI sysex bytes; the event buffer may be chained, thus it has + * to be expanded via snd_seq_dump_var_event(). + */ +struct readq_sysex_ctx { + struct seq_oss_readq *readq; + int dev; +}; + +static int readq_dump_sysex(void *ptr, void *buf, int count) +{ + struct readq_sysex_ctx *ctx = ptr; + + return snd_seq_oss_readq_puts(ctx->readq, ctx->dev, buf, count); +} + +int snd_seq_oss_readq_sysex(struct seq_oss_readq *q, int dev, + struct snd_seq_event *ev) +{ + struct readq_sysex_ctx ctx = { + .readq = q, + .dev = dev + }; + + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + return 0; + return snd_seq_dump_var_event(ev, readq_dump_sysex, &ctx); +} + +/* + * copy an event to input queue: + * return zero if enqueued + */ +int +snd_seq_oss_readq_put_event(struct seq_oss_readq *q, union evrec *ev) +{ + unsigned long flags; + + spin_lock_irqsave(&q->lock, flags); + if (q->qlen >= q->maxlen - 1) { + spin_unlock_irqrestore(&q->lock, flags); + return -ENOMEM; + } + + memcpy(&q->q[q->tail], ev, sizeof(*ev)); + q->tail = (q->tail + 1) % q->maxlen; + q->qlen++; + + /* wake up sleeper */ + wake_up(&q->midi_sleep); + + spin_unlock_irqrestore(&q->lock, flags); + + return 0; +} + + +/* + * pop queue + * caller must hold lock + */ +int +snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec) +{ + if (q->qlen == 0) + return -EAGAIN; + memcpy(rec, &q->q[q->head], sizeof(*rec)); + return 0; +} + +/* + * sleep until ready + */ +void +snd_seq_oss_readq_wait(struct seq_oss_readq *q) +{ + wait_event_interruptible_timeout(q->midi_sleep, + (q->qlen > 0 || q->head == q->tail), + q->pre_event_timeout); +} + +/* + * drain one record + * caller must hold lock + */ +void +snd_seq_oss_readq_free(struct seq_oss_readq *q) +{ + if (q->qlen > 0) { + q->head = (q->head + 1) % q->maxlen; + q->qlen--; + } +} + +/* + * polling/select: + * return non-zero if readq is not empty. + */ +unsigned int +snd_seq_oss_readq_poll(struct seq_oss_readq *q, struct file *file, poll_table *wait) +{ + poll_wait(file, &q->midi_sleep, wait); + return q->qlen; +} + +/* + * put a timestamp + */ +int +snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *q, unsigned long curt, int seq_mode) +{ + if (curt != q->input_time) { + union evrec rec; + memset(&rec, 0, sizeof(rec)); + switch (seq_mode) { + case SNDRV_SEQ_OSS_MODE_SYNTH: + rec.echo = (curt << 8) | SEQ_WAIT; + snd_seq_oss_readq_put_event(q, &rec); + break; + case SNDRV_SEQ_OSS_MODE_MUSIC: + rec.t.code = EV_TIMING; + rec.t.cmd = TMR_WAIT_ABS; + rec.t.time = curt; + snd_seq_oss_readq_put_event(q, &rec); + break; + } + q->input_time = curt; + } + return 0; +} + + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ +void +snd_seq_oss_readq_info_read(struct seq_oss_readq *q, struct snd_info_buffer *buf) +{ + snd_iprintf(buf, " read queue [%s] length = %d : tick = %ld\n", + (waitqueue_active(&q->midi_sleep) ? "sleeping":"running"), + q->qlen, q->input_time); +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_readq.h b/sound/core/seq/oss/seq_oss_readq.h new file mode 100644 index 0000000000..38d0c4682b --- /dev/null +++ b/sound/core/seq/oss/seq_oss_readq.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OSS compatible sequencer driver + * read fifo queue + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SEQ_OSS_READQ_H +#define __SEQ_OSS_READQ_H + +#include "seq_oss_device.h" + + +/* + * definition of read queue + */ +struct seq_oss_readq { + union evrec *q; + int qlen; + int maxlen; + int head, tail; + unsigned long pre_event_timeout; + unsigned long input_time; + wait_queue_head_t midi_sleep; + spinlock_t lock; +}; + +struct seq_oss_readq *snd_seq_oss_readq_new(struct seq_oss_devinfo *dp, int maxlen); +void snd_seq_oss_readq_delete(struct seq_oss_readq *q); +void snd_seq_oss_readq_clear(struct seq_oss_readq *readq); +unsigned int snd_seq_oss_readq_poll(struct seq_oss_readq *readq, struct file *file, poll_table *wait); +int snd_seq_oss_readq_puts(struct seq_oss_readq *readq, int dev, unsigned char *data, int len); +int snd_seq_oss_readq_sysex(struct seq_oss_readq *q, int dev, + struct snd_seq_event *ev); +int snd_seq_oss_readq_put_event(struct seq_oss_readq *readq, union evrec *ev); +int snd_seq_oss_readq_put_timestamp(struct seq_oss_readq *readq, unsigned long curt, int seq_mode); +int snd_seq_oss_readq_pick(struct seq_oss_readq *q, union evrec *rec); +void snd_seq_oss_readq_wait(struct seq_oss_readq *q); +void snd_seq_oss_readq_free(struct seq_oss_readq *q); + +#define snd_seq_oss_readq_lock(q, flags) spin_lock_irqsave(&(q)->lock, flags) +#define snd_seq_oss_readq_unlock(q, flags) spin_unlock_irqrestore(&(q)->lock, flags) + +#endif diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c new file mode 100644 index 0000000000..8a142fd54a --- /dev/null +++ b/sound/core/seq/oss/seq_oss_rw.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * read/write/select interface to device file + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_device.h" +#include "seq_oss_readq.h" +#include "seq_oss_writeq.h" +#include "seq_oss_synth.h" +#include <sound/seq_oss_legacy.h> +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include "../seq_clientmgr.h" + + +/* + * protoypes + */ +static int insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt); + + +/* + * read interface + */ + +int +snd_seq_oss_read(struct seq_oss_devinfo *dp, char __user *buf, int count) +{ + struct seq_oss_readq *readq = dp->readq; + int result = 0, err = 0; + int ev_len; + union evrec rec; + unsigned long flags; + + if (readq == NULL || ! is_read_mode(dp->file_mode)) + return -ENXIO; + + while (count >= SHORT_EVENT_SIZE) { + snd_seq_oss_readq_lock(readq, flags); + err = snd_seq_oss_readq_pick(readq, &rec); + if (err == -EAGAIN && + !is_nonblock_mode(dp->file_mode) && result == 0) { + snd_seq_oss_readq_unlock(readq, flags); + snd_seq_oss_readq_wait(readq); + snd_seq_oss_readq_lock(readq, flags); + if (signal_pending(current)) + err = -ERESTARTSYS; + else + err = snd_seq_oss_readq_pick(readq, &rec); + } + if (err < 0) { + snd_seq_oss_readq_unlock(readq, flags); + break; + } + ev_len = ev_length(&rec); + if (ev_len < count) { + snd_seq_oss_readq_unlock(readq, flags); + break; + } + snd_seq_oss_readq_free(readq); + snd_seq_oss_readq_unlock(readq, flags); + if (copy_to_user(buf, &rec, ev_len)) { + err = -EFAULT; + break; + } + result += ev_len; + buf += ev_len; + count -= ev_len; + } + return result > 0 ? result : err; +} + + +/* + * write interface + */ + +int +snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, struct file *opt) +{ + int result = 0, err = 0; + int ev_size, fmt; + union evrec rec; + + if (! is_write_mode(dp->file_mode) || dp->writeq == NULL) + return -ENXIO; + + while (count >= SHORT_EVENT_SIZE) { + if (copy_from_user(&rec, buf, SHORT_EVENT_SIZE)) { + err = -EFAULT; + break; + } + if (rec.s.code == SEQ_FULLSIZE) { + /* load patch */ + if (result > 0) { + err = -EINVAL; + break; + } + fmt = (*(unsigned short *)rec.c) & 0xffff; + /* FIXME the return value isn't correct */ + return snd_seq_oss_synth_load_patch(dp, rec.s.dev, + fmt, buf, 0, count); + } + if (ev_is_long(&rec)) { + /* extended code */ + if (rec.s.code == SEQ_EXTENDED && + dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + err = -EINVAL; + break; + } + ev_size = LONG_EVENT_SIZE; + if (count < ev_size) + break; + /* copy the reset 4 bytes */ + if (copy_from_user(rec.c + SHORT_EVENT_SIZE, + buf + SHORT_EVENT_SIZE, + LONG_EVENT_SIZE - SHORT_EVENT_SIZE)) { + err = -EFAULT; + break; + } + } else { + /* old-type code */ + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_MUSIC) { + err = -EINVAL; + break; + } + ev_size = SHORT_EVENT_SIZE; + } + + /* insert queue */ + err = insert_queue(dp, &rec, opt); + if (err < 0) + break; + + result += ev_size; + buf += ev_size; + count -= ev_size; + } + return result > 0 ? result : err; +} + + +/* + * insert event record to write queue + * return: 0 = OK, non-zero = NG + */ +static int +insert_queue(struct seq_oss_devinfo *dp, union evrec *rec, struct file *opt) +{ + int rc = 0; + struct snd_seq_event event; + + /* if this is a timing event, process the current time */ + if (snd_seq_oss_process_timer_event(dp->timer, rec)) + return 0; /* no need to insert queue */ + + /* parse this event */ + memset(&event, 0, sizeof(event)); + /* set dummy -- to be sure */ + event.type = SNDRV_SEQ_EVENT_NOTEOFF; + snd_seq_oss_fill_addr(dp, &event, dp->addr.client, dp->addr.port); + + if (snd_seq_oss_process_event(dp, rec, &event)) + return 0; /* invalid event - no need to insert queue */ + + event.time.tick = snd_seq_oss_timer_cur_tick(dp->timer); + if (dp->timer->realtime || !dp->timer->running) + snd_seq_oss_dispatch(dp, &event, 0, 0); + else + rc = snd_seq_kernel_client_enqueue(dp->cseq, &event, opt, + !is_nonblock_mode(dp->file_mode)); + return rc; +} + + +/* + * select / poll + */ + +__poll_t +snd_seq_oss_poll(struct seq_oss_devinfo *dp, struct file *file, poll_table * wait) +{ + __poll_t mask = 0; + + /* input */ + if (dp->readq && is_read_mode(dp->file_mode)) { + if (snd_seq_oss_readq_poll(dp->readq, file, wait)) + mask |= EPOLLIN | EPOLLRDNORM; + } + + /* output */ + if (dp->writeq && is_write_mode(dp->file_mode)) { + if (snd_seq_kernel_client_write_poll(dp->cseq, file, wait)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + return mask; +} diff --git a/sound/core/seq/oss/seq_oss_synth.c b/sound/core/seq/oss/seq_oss_synth.c new file mode 100644 index 0000000000..e3394919da --- /dev/null +++ b/sound/core/seq/oss/seq_oss_synth.c @@ -0,0 +1,666 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * synth device handlers + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_synth.h" +#include "seq_oss_midi.h" +#include "../seq_lock.h" +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/nospec.h> + +/* + * constants + */ +#define SNDRV_SEQ_OSS_MAX_SYNTH_NAME 30 +#define MAX_SYSEX_BUFLEN 128 + + +/* + * definition of synth info records + */ + +/* sysex buffer */ +struct seq_oss_synth_sysex { + int len; + int skip; + unsigned char buf[MAX_SYSEX_BUFLEN]; +}; + +/* synth info */ +struct seq_oss_synth { + int seq_device; + + /* for synth_info */ + int synth_type; + int synth_subtype; + int nr_voices; + + char name[SNDRV_SEQ_OSS_MAX_SYNTH_NAME]; + struct snd_seq_oss_callback oper; + + int opened; + + void *private_data; + snd_use_lock_t use_lock; +}; + + +/* + * device table + */ +static int max_synth_devs; +static struct seq_oss_synth *synth_devs[SNDRV_SEQ_OSS_MAX_SYNTH_DEVS]; +static struct seq_oss_synth midi_synth_dev = { + .seq_device = -1, + .synth_type = SYNTH_TYPE_MIDI, + .synth_subtype = 0, + .nr_voices = 16, + .name = "MIDI", +}; + +static DEFINE_SPINLOCK(register_lock); + +/* + * prototypes + */ +static struct seq_oss_synth *get_synthdev(struct seq_oss_devinfo *dp, int dev); +static void reset_channels(struct seq_oss_synthinfo *info); + +/* + * global initialization + */ +void __init +snd_seq_oss_synth_init(void) +{ + snd_use_lock_init(&midi_synth_dev.use_lock); +} + +/* + * registration of the synth device + */ +int +snd_seq_oss_synth_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + int i; + struct seq_oss_synth *rec; + struct snd_seq_oss_reg *reg = SNDRV_SEQ_DEVICE_ARGPTR(dev); + unsigned long flags; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return -ENOMEM; + rec->seq_device = -1; + rec->synth_type = reg->type; + rec->synth_subtype = reg->subtype; + rec->nr_voices = reg->nvoices; + rec->oper = reg->oper; + rec->private_data = reg->private_data; + rec->opened = 0; + snd_use_lock_init(&rec->use_lock); + + /* copy and truncate the name of synth device */ + strscpy(rec->name, dev->name, sizeof(rec->name)); + + /* registration */ + spin_lock_irqsave(®ister_lock, flags); + for (i = 0; i < max_synth_devs; i++) { + if (synth_devs[i] == NULL) + break; + } + if (i >= max_synth_devs) { + if (max_synth_devs >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) { + spin_unlock_irqrestore(®ister_lock, flags); + pr_err("ALSA: seq_oss: no more synth slot\n"); + kfree(rec); + return -ENOMEM; + } + max_synth_devs++; + } + rec->seq_device = i; + synth_devs[i] = rec; + spin_unlock_irqrestore(®ister_lock, flags); + dev->driver_data = rec; +#ifdef SNDRV_OSS_INFO_DEV_SYNTH + if (i < SNDRV_CARDS) + snd_oss_info_register(SNDRV_OSS_INFO_DEV_SYNTH, i, rec->name); +#endif + return 0; +} + + +int +snd_seq_oss_synth_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + int index; + struct seq_oss_synth *rec = dev->driver_data; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + for (index = 0; index < max_synth_devs; index++) { + if (synth_devs[index] == rec) + break; + } + if (index >= max_synth_devs) { + spin_unlock_irqrestore(®ister_lock, flags); + pr_err("ALSA: seq_oss: can't unregister synth\n"); + return -EINVAL; + } + synth_devs[index] = NULL; + if (index == max_synth_devs - 1) { + for (index--; index >= 0; index--) { + if (synth_devs[index]) + break; + } + max_synth_devs = index + 1; + } + spin_unlock_irqrestore(®ister_lock, flags); +#ifdef SNDRV_OSS_INFO_DEV_SYNTH + if (rec->seq_device < SNDRV_CARDS) + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_SYNTH, rec->seq_device); +#endif + + snd_use_lock_sync(&rec->use_lock); + kfree(rec); + + return 0; +} + + +/* + */ +static struct seq_oss_synth * +get_sdev(int dev) +{ + struct seq_oss_synth *rec; + unsigned long flags; + + spin_lock_irqsave(®ister_lock, flags); + rec = synth_devs[dev]; + if (rec) + snd_use_lock_use(&rec->use_lock); + spin_unlock_irqrestore(®ister_lock, flags); + return rec; +} + + +/* + * set up synth tables + */ + +void +snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp) +{ + int i; + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + + dp->max_synthdev = max_synth_devs; + dp->synth_opened = 0; + memset(dp->synths, 0, sizeof(dp->synths)); + for (i = 0; i < dp->max_synthdev; i++) { + rec = get_sdev(i); + if (rec == NULL) + continue; + if (rec->oper.open == NULL || rec->oper.close == NULL) { + snd_use_lock_free(&rec->use_lock); + continue; + } + info = &dp->synths[i]; + info->arg.app_index = dp->port; + info->arg.file_mode = dp->file_mode; + info->arg.seq_mode = dp->seq_mode; + if (dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) + info->arg.event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + else + info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS; + info->opened = 0; + if (!try_module_get(rec->oper.owner)) { + snd_use_lock_free(&rec->use_lock); + continue; + } + if (rec->oper.open(&info->arg, rec->private_data) < 0) { + module_put(rec->oper.owner); + snd_use_lock_free(&rec->use_lock); + continue; + } + info->nr_voices = rec->nr_voices; + if (info->nr_voices > 0) { + info->ch = kcalloc(info->nr_voices, sizeof(struct seq_oss_chinfo), GFP_KERNEL); + if (!info->ch) { + rec->oper.close(&info->arg); + module_put(rec->oper.owner); + snd_use_lock_free(&rec->use_lock); + continue; + } + reset_channels(info); + } + info->opened++; + rec->opened++; + dp->synth_opened++; + snd_use_lock_free(&rec->use_lock); + } +} + + +/* + * set up synth tables for MIDI emulation - /dev/music mode only + */ + +void +snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp) +{ + int i; + + if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) + return; + + for (i = 0; i < dp->max_mididev; i++) { + struct seq_oss_synthinfo *info; + info = &dp->synths[dp->max_synthdev]; + if (snd_seq_oss_midi_open(dp, i, dp->file_mode) < 0) + continue; + info->arg.app_index = dp->port; + info->arg.file_mode = dp->file_mode; + info->arg.seq_mode = dp->seq_mode; + info->arg.private_data = info; + info->is_midi = 1; + info->midi_mapped = i; + info->arg.event_passing = SNDRV_SEQ_OSS_PASS_EVENTS; + snd_seq_oss_midi_get_addr(dp, i, &info->arg.addr); + info->opened = 1; + midi_synth_dev.opened++; + dp->max_synthdev++; + if (dp->max_synthdev >= SNDRV_SEQ_OSS_MAX_SYNTH_DEVS) + break; + } +} + + +/* + * clean up synth tables + */ + +void +snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp) +{ + int i; + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + + if (snd_BUG_ON(dp->max_synthdev > SNDRV_SEQ_OSS_MAX_SYNTH_DEVS)) + return; + for (i = 0; i < dp->max_synthdev; i++) { + info = &dp->synths[i]; + if (! info->opened) + continue; + if (info->is_midi) { + if (midi_synth_dev.opened > 0) { + snd_seq_oss_midi_close(dp, info->midi_mapped); + midi_synth_dev.opened--; + } + } else { + rec = get_sdev(i); + if (rec == NULL) + continue; + if (rec->opened > 0) { + rec->oper.close(&info->arg); + module_put(rec->oper.owner); + rec->opened = 0; + } + snd_use_lock_free(&rec->use_lock); + } + kfree(info->sysex); + info->sysex = NULL; + kfree(info->ch); + info->ch = NULL; + } + dp->synth_opened = 0; + dp->max_synthdev = 0; +} + +static struct seq_oss_synthinfo * +get_synthinfo_nospec(struct seq_oss_devinfo *dp, int dev) +{ + if (dev < 0 || dev >= dp->max_synthdev) + return NULL; + dev = array_index_nospec(dev, SNDRV_SEQ_OSS_MAX_SYNTH_DEVS); + return &dp->synths[dev]; +} + +/* + * return synth device information pointer + */ +static struct seq_oss_synth * +get_synthdev(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info = get_synthinfo_nospec(dp, dev); + + if (!info) + return NULL; + if (!info->opened) + return NULL; + if (info->is_midi) { + rec = &midi_synth_dev; + snd_use_lock_use(&rec->use_lock); + } else { + rec = get_sdev(dev); + if (!rec) + return NULL; + } + if (! rec->opened) { + snd_use_lock_free(&rec->use_lock); + return NULL; + } + return rec; +} + + +/* + * reset note and velocity on each channel. + */ +static void +reset_channels(struct seq_oss_synthinfo *info) +{ + int i; + if (info->ch == NULL || ! info->nr_voices) + return; + for (i = 0; i < info->nr_voices; i++) { + info->ch[i].note = -1; + info->ch[i].vel = 0; + } +} + + +/* + * reset synth device: + * call reset callback. if no callback is defined, send a heartbeat + * event to the corresponding port. + */ +void +snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + + info = get_synthinfo_nospec(dp, dev); + if (!info || !info->opened) + return; + if (info->sysex) + info->sysex->len = 0; /* reset sysex */ + reset_channels(info); + if (info->is_midi) { + if (midi_synth_dev.opened <= 0) + return; + snd_seq_oss_midi_reset(dp, info->midi_mapped); + /* reopen the device */ + snd_seq_oss_midi_close(dp, dev); + if (snd_seq_oss_midi_open(dp, info->midi_mapped, + dp->file_mode) < 0) { + midi_synth_dev.opened--; + info->opened = 0; + kfree(info->sysex); + info->sysex = NULL; + kfree(info->ch); + info->ch = NULL; + } + return; + } + + rec = get_sdev(dev); + if (rec == NULL) + return; + if (rec->oper.reset) { + rec->oper.reset(&info->arg); + } else { + struct snd_seq_event ev; + memset(&ev, 0, sizeof(ev)); + snd_seq_oss_fill_addr(dp, &ev, info->arg.addr.client, + info->arg.addr.port); + ev.type = SNDRV_SEQ_EVENT_RESET; + snd_seq_oss_dispatch(dp, &ev, 0, 0); + } + snd_use_lock_free(&rec->use_lock); +} + + +/* + * load a patch record: + * call load_patch callback function + */ +int +snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt, + const char __user *buf, int p, int c) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + int rc; + + info = get_synthinfo_nospec(dp, dev); + if (!info) + return -ENXIO; + + if (info->is_midi) + return 0; + rec = get_synthdev(dp, dev); + if (!rec) + return -ENXIO; + + if (rec->oper.load_patch == NULL) + rc = -ENXIO; + else + rc = rec->oper.load_patch(&info->arg, fmt, buf, p, c); + snd_use_lock_free(&rec->use_lock); + return rc; +} + +/* + * check if the device is valid synth device and return the synth info + */ +struct seq_oss_synthinfo * +snd_seq_oss_synth_info(struct seq_oss_devinfo *dp, int dev) +{ + struct seq_oss_synth *rec; + + rec = get_synthdev(dp, dev); + if (rec) { + snd_use_lock_free(&rec->use_lock); + return get_synthinfo_nospec(dp, dev); + } + return NULL; +} + + +/* + * receive OSS 6 byte sysex packet: + * the full sysex message will be sent if it reaches to the end of data + * (0xff). + */ +int +snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, struct snd_seq_event *ev) +{ + int i, send; + unsigned char *dest; + struct seq_oss_synth_sysex *sysex; + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info) + return -ENXIO; + + sysex = info->sysex; + if (sysex == NULL) { + sysex = kzalloc(sizeof(*sysex), GFP_KERNEL); + if (sysex == NULL) + return -ENOMEM; + info->sysex = sysex; + } + + send = 0; + dest = sysex->buf + sysex->len; + /* copy 6 byte packet to the buffer */ + for (i = 0; i < 6; i++) { + if (buf[i] == 0xff) { + send = 1; + break; + } + dest[i] = buf[i]; + sysex->len++; + if (sysex->len >= MAX_SYSEX_BUFLEN) { + sysex->len = 0; + sysex->skip = 1; + break; + } + } + + if (sysex->len && send) { + if (sysex->skip) { + sysex->skip = 0; + sysex->len = 0; + return -EINVAL; /* skip */ + } + /* copy the data to event record and send it */ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + if (snd_seq_oss_synth_addr(dp, dev, ev)) + return -EINVAL; + ev->data.ext.len = sysex->len; + ev->data.ext.ptr = sysex->buf; + sysex->len = 0; + return 0; + } + + return -EINVAL; /* skip */ +} + +/* + * fill the event source/destination addresses + */ +int +snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info = snd_seq_oss_synth_info(dp, dev); + + if (!info) + return -EINVAL; + snd_seq_oss_fill_addr(dp, ev, info->arg.addr.client, + info->arg.addr.port); + return 0; +} + + +/* + * OSS compatible ioctl + */ +int +snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd, unsigned long addr) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info; + int rc; + + info = get_synthinfo_nospec(dp, dev); + if (!info || info->is_midi) + return -ENXIO; + rec = get_synthdev(dp, dev); + if (!rec) + return -ENXIO; + if (rec->oper.ioctl == NULL) + rc = -ENXIO; + else + rc = rec->oper.ioctl(&info->arg, cmd, addr); + snd_use_lock_free(&rec->use_lock); + return rc; +} + + +/* + * send OSS raw events - SEQ_PRIVATE and SEQ_VOLUME + */ +int +snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev, unsigned char *data, struct snd_seq_event *ev) +{ + struct seq_oss_synthinfo *info; + + info = snd_seq_oss_synth_info(dp, dev); + if (!info || info->is_midi) + return -ENXIO; + ev->type = SNDRV_SEQ_EVENT_OSS; + memcpy(ev->data.raw8.d, data, 8); + return snd_seq_oss_synth_addr(dp, dev, ev); +} + + +/* + * create OSS compatible synth_info record + */ +int +snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf) +{ + struct seq_oss_synth *rec; + struct seq_oss_synthinfo *info = get_synthinfo_nospec(dp, dev); + + if (!info) + return -ENXIO; + + if (info->is_midi) { + struct midi_info minf; + if (snd_seq_oss_midi_make_info(dp, info->midi_mapped, &minf)) + return -ENXIO; + inf->synth_type = SYNTH_TYPE_MIDI; + inf->synth_subtype = 0; + inf->nr_voices = 16; + inf->device = dev; + strscpy(inf->name, minf.name, sizeof(inf->name)); + } else { + rec = get_synthdev(dp, dev); + if (!rec) + return -ENXIO; + inf->synth_type = rec->synth_type; + inf->synth_subtype = rec->synth_subtype; + inf->nr_voices = rec->nr_voices; + inf->device = dev; + strscpy(inf->name, rec->name, sizeof(inf->name)); + snd_use_lock_free(&rec->use_lock); + } + return 0; +} + + +#ifdef CONFIG_SND_PROC_FS +/* + * proc interface + */ +void +snd_seq_oss_synth_info_read(struct snd_info_buffer *buf) +{ + int i; + struct seq_oss_synth *rec; + + snd_iprintf(buf, "\nNumber of synth devices: %d\n", max_synth_devs); + for (i = 0; i < max_synth_devs; i++) { + snd_iprintf(buf, "\nsynth %d: ", i); + rec = get_sdev(i); + if (rec == NULL) { + snd_iprintf(buf, "*empty*\n"); + continue; + } + snd_iprintf(buf, "[%s]\n", rec->name); + snd_iprintf(buf, " type 0x%x : subtype 0x%x : voices %d\n", + rec->synth_type, rec->synth_subtype, + rec->nr_voices); + snd_iprintf(buf, " capabilities : ioctl %s / load_patch %s\n", + enabled_str((long)rec->oper.ioctl), + enabled_str((long)rec->oper.load_patch)); + snd_use_lock_free(&rec->use_lock); + } +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/seq/oss/seq_oss_synth.h b/sound/core/seq/oss/seq_oss_synth.h new file mode 100644 index 0000000000..ffc40d8a7e --- /dev/null +++ b/sound/core/seq/oss/seq_oss_synth.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OSS compatible sequencer driver + * + * synth device information + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SEQ_OSS_SYNTH_H +#define __SEQ_OSS_SYNTH_H + +#include "seq_oss_device.h" +#include <sound/seq_oss_legacy.h> +#include <sound/seq_device.h> + +void snd_seq_oss_synth_init(void); +int snd_seq_oss_synth_probe(struct device *dev); +int snd_seq_oss_synth_remove(struct device *dev); +void snd_seq_oss_synth_setup(struct seq_oss_devinfo *dp); +void snd_seq_oss_synth_setup_midi(struct seq_oss_devinfo *dp); +void snd_seq_oss_synth_cleanup(struct seq_oss_devinfo *dp); + +void snd_seq_oss_synth_reset(struct seq_oss_devinfo *dp, int dev); +int snd_seq_oss_synth_load_patch(struct seq_oss_devinfo *dp, int dev, int fmt, + const char __user *buf, int p, int c); +struct seq_oss_synthinfo *snd_seq_oss_synth_info(struct seq_oss_devinfo *dp, + int dev); +int snd_seq_oss_synth_sysex(struct seq_oss_devinfo *dp, int dev, unsigned char *buf, + struct snd_seq_event *ev); +int snd_seq_oss_synth_addr(struct seq_oss_devinfo *dp, int dev, struct snd_seq_event *ev); +int snd_seq_oss_synth_ioctl(struct seq_oss_devinfo *dp, int dev, unsigned int cmd, + unsigned long addr); +int snd_seq_oss_synth_raw_event(struct seq_oss_devinfo *dp, int dev, + unsigned char *data, struct snd_seq_event *ev); + +int snd_seq_oss_synth_make_info(struct seq_oss_devinfo *dp, int dev, struct synth_info *inf); + +#endif diff --git a/sound/core/seq/oss/seq_oss_timer.c b/sound/core/seq/oss/seq_oss_timer.c new file mode 100644 index 0000000000..f9f57232a8 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_timer.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * Timer control routines + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_timer.h" +#include "seq_oss_event.h" +#include <sound/seq_oss_legacy.h> +#include <linux/slab.h> + +/* + */ +#define MIN_OSS_TEMPO 8 +#define MAX_OSS_TEMPO 360 +#define MIN_OSS_TIMEBASE 1 +#define MAX_OSS_TIMEBASE 1000 + +/* + */ +static void calc_alsa_tempo(struct seq_oss_timer *timer); +static int send_timer_event(struct seq_oss_devinfo *dp, int type, int value); + + +/* + * create and register a new timer. + * if queue is not started yet, start it. + */ +struct seq_oss_timer * +snd_seq_oss_timer_new(struct seq_oss_devinfo *dp) +{ + struct seq_oss_timer *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (rec == NULL) + return NULL; + + rec->dp = dp; + rec->cur_tick = 0; + rec->realtime = 0; + rec->running = 0; + rec->oss_tempo = 60; + rec->oss_timebase = 100; + calc_alsa_tempo(rec); + + return rec; +} + + +/* + * delete timer. + * if no more timer exists, stop the queue. + */ +void +snd_seq_oss_timer_delete(struct seq_oss_timer *rec) +{ + if (rec) { + snd_seq_oss_timer_stop(rec); + kfree(rec); + } +} + + +/* + * process one timing event + * return 1 : event proceseed -- skip this event + * 0 : not a timer event -- enqueue this event + */ +int +snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *ev) +{ + abstime_t parm = ev->t.time; + + if (ev->t.code == EV_TIMING) { + switch (ev->t.cmd) { + case TMR_WAIT_REL: + parm += rec->cur_tick; + rec->realtime = 0; + fallthrough; + case TMR_WAIT_ABS: + if (parm == 0) { + rec->realtime = 1; + } else if (parm >= rec->cur_tick) { + rec->realtime = 0; + rec->cur_tick = parm; + } + return 1; /* skip this event */ + + case TMR_START: + snd_seq_oss_timer_start(rec); + return 1; + + } + } else if (ev->s.code == SEQ_WAIT) { + /* time = from 1 to 3 bytes */ + parm = (ev->echo >> 8) & 0xffffff; + if (parm > rec->cur_tick) { + /* set next event time */ + rec->cur_tick = parm; + rec->realtime = 0; + } + return 1; + } + + return 0; +} + + +/* + * convert tempo units + */ +static void +calc_alsa_tempo(struct seq_oss_timer *timer) +{ + timer->tempo = (60 * 1000000) / timer->oss_tempo; + timer->ppq = timer->oss_timebase; +} + + +/* + * dispatch a timer event + */ +static int +send_timer_event(struct seq_oss_devinfo *dp, int type, int value) +{ + struct snd_seq_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = type; + ev.source.client = dp->cseq; + ev.source.port = 0; + ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM; + ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + ev.queue = dp->queue; + ev.data.queue.queue = dp->queue; + ev.data.queue.param.value = value; + return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0); +} + +/* + * set queue tempo and start queue + */ +int +snd_seq_oss_timer_start(struct seq_oss_timer *timer) +{ + struct seq_oss_devinfo *dp = timer->dp; + struct snd_seq_queue_tempo tmprec; + + if (timer->running) + snd_seq_oss_timer_stop(timer); + + memset(&tmprec, 0, sizeof(tmprec)); + tmprec.queue = dp->queue; + tmprec.ppq = timer->ppq; + tmprec.tempo = timer->tempo; + snd_seq_set_queue_tempo(dp->cseq, &tmprec); + + send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0); + timer->running = 1; + timer->cur_tick = 0; + return 0; +} + + +/* + * stop queue + */ +int +snd_seq_oss_timer_stop(struct seq_oss_timer *timer) +{ + if (! timer->running) + return 0; + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0); + timer->running = 0; + return 0; +} + + +/* + * continue queue + */ +int +snd_seq_oss_timer_continue(struct seq_oss_timer *timer) +{ + if (timer->running) + return 0; + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0); + timer->running = 1; + return 0; +} + + +/* + * change queue tempo + */ +int +snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value) +{ + if (value < MIN_OSS_TEMPO) + value = MIN_OSS_TEMPO; + else if (value > MAX_OSS_TEMPO) + value = MAX_OSS_TEMPO; + timer->oss_tempo = value; + calc_alsa_tempo(timer); + if (timer->running) + send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo); + return 0; +} + + +/* + * ioctls + */ +int +snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg) +{ + int value; + + if (cmd == SNDCTL_SEQ_CTRLRATE) { + /* if *arg == 0, just return the current rate */ + if (get_user(value, arg)) + return -EFAULT; + if (value) + return -EINVAL; + value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60; + return put_user(value, arg) ? -EFAULT : 0; + } + + if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH) + return 0; + + switch (cmd) { + case SNDCTL_TMR_START: + return snd_seq_oss_timer_start(timer); + case SNDCTL_TMR_STOP: + return snd_seq_oss_timer_stop(timer); + case SNDCTL_TMR_CONTINUE: + return snd_seq_oss_timer_continue(timer); + case SNDCTL_TMR_TEMPO: + if (get_user(value, arg)) + return -EFAULT; + return snd_seq_oss_timer_tempo(timer, value); + case SNDCTL_TMR_TIMEBASE: + if (get_user(value, arg)) + return -EFAULT; + if (value < MIN_OSS_TIMEBASE) + value = MIN_OSS_TIMEBASE; + else if (value > MAX_OSS_TIMEBASE) + value = MAX_OSS_TIMEBASE; + timer->oss_timebase = value; + calc_alsa_tempo(timer); + return 0; + + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SELECT: + case SNDCTL_TMR_SOURCE: + /* not supported */ + return 0; + } + return 0; +} diff --git a/sound/core/seq/oss/seq_oss_timer.h b/sound/core/seq/oss/seq_oss_timer.h new file mode 100644 index 0000000000..dee190b4ec --- /dev/null +++ b/sound/core/seq/oss/seq_oss_timer.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OSS compatible sequencer driver + * timer handling routines + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SEQ_OSS_TIMER_H +#define __SEQ_OSS_TIMER_H + +#include "seq_oss_device.h" + +/* + * timer information definition + */ +struct seq_oss_timer { + struct seq_oss_devinfo *dp; + reltime_t cur_tick; + int realtime; + int running; + int tempo, ppq; /* ALSA queue */ + int oss_tempo, oss_timebase; +}; + + +struct seq_oss_timer *snd_seq_oss_timer_new(struct seq_oss_devinfo *dp); +void snd_seq_oss_timer_delete(struct seq_oss_timer *dp); + +int snd_seq_oss_timer_start(struct seq_oss_timer *timer); +int snd_seq_oss_timer_stop(struct seq_oss_timer *timer); +int snd_seq_oss_timer_continue(struct seq_oss_timer *timer); +int snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value); +#define snd_seq_oss_timer_reset snd_seq_oss_timer_start + +int snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg); + +/* + * get current processed time + */ +static inline abstime_t +snd_seq_oss_timer_cur_tick(struct seq_oss_timer *timer) +{ + return timer->cur_tick; +} + +#endif diff --git a/sound/core/seq/oss/seq_oss_writeq.c b/sound/core/seq/oss/seq_oss_writeq.c new file mode 100644 index 0000000000..3e3209ce53 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_writeq.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OSS compatible sequencer driver + * + * seq_oss_writeq.c - write queue and sync + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#include "seq_oss_writeq.h" +#include "seq_oss_event.h" +#include "seq_oss_timer.h" +#include <sound/seq_oss_legacy.h> +#include "../seq_lock.h" +#include "../seq_clientmgr.h" +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + + +/* + * create a write queue record + */ +struct seq_oss_writeq * +snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen) +{ + struct seq_oss_writeq *q; + struct snd_seq_client_pool pool; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + q->dp = dp; + q->maxlen = maxlen; + spin_lock_init(&q->sync_lock); + q->sync_event_put = 0; + q->sync_time = 0; + init_waitqueue_head(&q->sync_sleep); + + memset(&pool, 0, sizeof(pool)); + pool.client = dp->cseq; + pool.output_pool = maxlen; + pool.output_room = maxlen / 2; + + snd_seq_oss_control(dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool); + + return q; +} + +/* + * delete the write queue + */ +void +snd_seq_oss_writeq_delete(struct seq_oss_writeq *q) +{ + if (q) { + snd_seq_oss_writeq_clear(q); /* to be sure */ + kfree(q); + } +} + + +/* + * reset the write queue + */ +void +snd_seq_oss_writeq_clear(struct seq_oss_writeq *q) +{ + struct snd_seq_remove_events reset; + + memset(&reset, 0, sizeof(reset)); + reset.remove_mode = SNDRV_SEQ_REMOVE_OUTPUT; /* remove all */ + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_REMOVE_EVENTS, &reset); + + /* wake up sleepers if any */ + snd_seq_oss_writeq_wakeup(q, 0); +} + +/* + * wait until the write buffer has enough room + */ +int +snd_seq_oss_writeq_sync(struct seq_oss_writeq *q) +{ + struct seq_oss_devinfo *dp = q->dp; + abstime_t time; + + time = snd_seq_oss_timer_cur_tick(dp->timer); + if (q->sync_time >= time) + return 0; /* already finished */ + + if (! q->sync_event_put) { + struct snd_seq_event ev; + union evrec *rec; + + /* put echoback event */ + memset(&ev, 0, sizeof(ev)); + ev.flags = 0; + ev.type = SNDRV_SEQ_EVENT_ECHO; + ev.time.tick = time; + /* echo back to itself */ + snd_seq_oss_fill_addr(dp, &ev, dp->addr.client, dp->addr.port); + rec = (union evrec *)&ev.data; + rec->t.code = SEQ_SYNCTIMER; + rec->t.time = time; + q->sync_event_put = 1; + snd_seq_kernel_client_enqueue(dp->cseq, &ev, NULL, true); + } + + wait_event_interruptible_timeout(q->sync_sleep, ! q->sync_event_put, HZ); + if (signal_pending(current)) + /* interrupted - return 0 to finish sync */ + q->sync_event_put = 0; + if (! q->sync_event_put || q->sync_time >= time) + return 0; + return 1; +} + +/* + * wake up sync - echo event was catched + */ +void +snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time) +{ + unsigned long flags; + + spin_lock_irqsave(&q->sync_lock, flags); + q->sync_time = time; + q->sync_event_put = 0; + wake_up(&q->sync_sleep); + spin_unlock_irqrestore(&q->sync_lock, flags); +} + + +/* + * return the unused pool size + */ +int +snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q) +{ + struct snd_seq_client_pool pool; + pool.client = q->dp->cseq; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool); + return pool.output_free; +} + + +/* + * set output threshold size from ioctl + */ +void +snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int val) +{ + struct snd_seq_client_pool pool; + pool.client = q->dp->cseq; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, &pool); + pool.output_room = val; + snd_seq_oss_control(q->dp, SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, &pool); +} + diff --git a/sound/core/seq/oss/seq_oss_writeq.h b/sound/core/seq/oss/seq_oss_writeq.h new file mode 100644 index 0000000000..490d27a7b2 --- /dev/null +++ b/sound/core/seq/oss/seq_oss_writeq.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OSS compatible sequencer driver + * write priority queue + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de> + */ + +#ifndef __SEQ_OSS_WRITEQ_H +#define __SEQ_OSS_WRITEQ_H + +#include "seq_oss_device.h" + + +struct seq_oss_writeq { + struct seq_oss_devinfo *dp; + int maxlen; + abstime_t sync_time; + int sync_event_put; + wait_queue_head_t sync_sleep; + spinlock_t sync_lock; +}; + + +/* + * seq_oss_writeq.c + */ +struct seq_oss_writeq *snd_seq_oss_writeq_new(struct seq_oss_devinfo *dp, int maxlen); +void snd_seq_oss_writeq_delete(struct seq_oss_writeq *q); +void snd_seq_oss_writeq_clear(struct seq_oss_writeq *q); +int snd_seq_oss_writeq_sync(struct seq_oss_writeq *q); +void snd_seq_oss_writeq_wakeup(struct seq_oss_writeq *q, abstime_t time); +int snd_seq_oss_writeq_get_free_size(struct seq_oss_writeq *q); +void snd_seq_oss_writeq_set_output(struct seq_oss_writeq *q, int size); + + +#endif diff --git a/sound/core/seq/seq.c b/sound/core/seq/seq.c new file mode 100644 index 0000000000..00f7342ee8 --- /dev/null +++ b/sound/core/seq/seq.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer main module + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/initval.h> + +#include <sound/seq_kernel.h> +#include "seq_clientmgr.h" +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_lock.h" +#include "seq_timer.h" +#include "seq_system.h" +#include "seq_info.h" +#include <sound/minors.h> +#include <sound/seq_device.h> + +#if defined(CONFIG_SND_SEQ_DUMMY_MODULE) +int seq_client_load[15] = {[0] = SNDRV_SEQ_CLIENT_DUMMY, [1 ... 14] = -1}; +#else +int seq_client_load[15] = {[0 ... 14] = -1}; +#endif +int seq_default_timer_class = SNDRV_TIMER_CLASS_GLOBAL; +int seq_default_timer_sclass = SNDRV_TIMER_SCLASS_NONE; +int seq_default_timer_card = -1; +int seq_default_timer_device = +#ifdef CONFIG_SND_SEQ_HRTIMER_DEFAULT + SNDRV_TIMER_GLOBAL_HRTIMER +#else + SNDRV_TIMER_GLOBAL_SYSTEM +#endif + ; +int seq_default_timer_subdevice = 0; +int seq_default_timer_resolution = 0; /* Hz */ + +MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer."); +MODULE_LICENSE("GPL"); + +module_param_array(seq_client_load, int, NULL, 0444); +MODULE_PARM_DESC(seq_client_load, "The numbers of global (system) clients to load through kmod."); +module_param(seq_default_timer_class, int, 0644); +MODULE_PARM_DESC(seq_default_timer_class, "The default timer class."); +module_param(seq_default_timer_sclass, int, 0644); +MODULE_PARM_DESC(seq_default_timer_sclass, "The default timer slave class."); +module_param(seq_default_timer_card, int, 0644); +MODULE_PARM_DESC(seq_default_timer_card, "The default timer card number."); +module_param(seq_default_timer_device, int, 0644); +MODULE_PARM_DESC(seq_default_timer_device, "The default timer device number."); +module_param(seq_default_timer_subdevice, int, 0644); +MODULE_PARM_DESC(seq_default_timer_subdevice, "The default timer subdevice number."); +module_param(seq_default_timer_resolution, int, 0644); +MODULE_PARM_DESC(seq_default_timer_resolution, "The default timer resolution in Hz."); + +MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_SEQUENCER); +MODULE_ALIAS("devname:snd/seq"); + +/* + * INIT PART + */ + +static int __init alsa_seq_init(void) +{ + int err; + + err = client_init_data(); + if (err < 0) + goto error; + + /* register sequencer device */ + err = snd_sequencer_device_init(); + if (err < 0) + goto error; + + /* register proc interface */ + err = snd_seq_info_init(); + if (err < 0) + goto error_device; + + /* register our internal client */ + err = snd_seq_system_client_init(); + if (err < 0) + goto error_info; + + snd_seq_autoload_init(); + return 0; + + error_info: + snd_seq_info_done(); + error_device: + snd_sequencer_device_done(); + error: + return err; +} + +static void __exit alsa_seq_exit(void) +{ + /* unregister our internal client */ + snd_seq_system_client_done(); + + /* unregister proc interface */ + snd_seq_info_done(); + + /* delete timing queues */ + snd_seq_queues_delete(); + + /* unregister sequencer device */ + snd_sequencer_device_done(); + + snd_seq_autoload_exit(); +} + +module_init(alsa_seq_init) +module_exit(alsa_seq_exit) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c new file mode 100644 index 0000000000..42a7051410 --- /dev/null +++ b/sound/core/seq/seq_clientmgr.c @@ -0,0 +1,2759 @@ +// 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 *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 filter, 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 */ +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 clienttab[clientid]; +} + +struct snd_seq_client *snd_seq_client_use_ptr(int clientid) +{ + unsigned long flags; + 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; + } + 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_MODULES + if (!in_interrupt()) { + 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(); + } + } + 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; +} + +/* Take refcount and perform ioctl_mutex lock on the given client; + * used only for OSS sequencer + * Unlock via snd_seq_client_ioctl_unlock() below + */ +bool snd_seq_client_ioctl_lock(int clientid) +{ + struct snd_seq_client *client; + + client = snd_seq_client_use_ptr(clientid); + if (!client) + return false; + mutex_lock(&client->ioctl_mutex); + /* The client isn't unrefed here; see snd_seq_client_ioctl_unlock() */ + return true; +} +EXPORT_SYMBOL_GPL(snd_seq_client_ioctl_lock); + +/* Unlock and unref the given client; for OSS sequencer use only */ +void snd_seq_client_ioctl_unlock(int clientid) +{ + struct snd_seq_client *client; + + client = snd_seq_client_use_ptr(clientid); + if (WARN_ON(!client)) + return; + mutex_unlock(&client->ioctl_mutex); + /* The doubly unrefs below are intentional; the first one releases the + * leftover from snd_seq_client_ioctl_lock() above, and the second one + * is for releasing snd_seq_client_use_ptr() in this function + */ + snd_seq_client_unlock(client); + snd_seq_client_unlock(client); +} +EXPORT_SYMBOL_GPL(snd_seq_client_ioctl_unlock); + +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(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); + 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 */ + spin_lock_irq(&clients_lock); + if (client_index < 0) { + for (c = SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN; + c < SNDRV_SEQ_MAX_CLIENTS; + c++) { + if (clienttab[c] || clienttablock[c]) + continue; + clienttab[client->number = c] = client; + spin_unlock_irq(&clients_lock); + return client; + } + } else { + if (clienttab[client_index] == NULL && !clienttablock[client_index]) { + clienttab[client->number = client_index] = client; + spin_unlock_irq(&clients_lock); + return client; + } + } + spin_unlock_irq(&clients_lock); + 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; + spin_lock_irq(&clients_lock); + clienttablock[client->number] = 1; + clienttab[client->number] = NULL; + spin_unlock_irq(&clients_lock); + snd_seq_delete_all_ports(client); + snd_seq_queue_client_leave(client->number); + snd_use_lock_sync(&client->use_lock); + if (client->pool) + snd_seq_pool_delete(&client->pool); + spin_lock_irq(&clients_lock); + clienttablock[client->number] = 0; + spin_unlock_irq(&clients_lock); + return 0; +} + + +static void seq_free_client(struct snd_seq_client * client) +{ + mutex_lock(®ister_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); + } + mutex_unlock(®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 */ + struct snd_seq_client *client; + struct snd_seq_user_client *user; + int err; + + err = stream_open(inode, file); + if (err < 0) + return err; + + mutex_lock(®ister_mutex); + client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS); + if (!client) { + mutex_unlock(®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); + mutex_unlock(®ister_mutex); + return -ENOMEM; + } + } + + usage_alloc(&client_usage, 1); + client->type = USER_CLIENT; + mutex_unlock(®ister_mutex); + + 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; + snd_seq_fifo_lock(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; + 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; + } + snd_seq_fifo_unlock(fifo); + + 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) +{ + 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 struct snd_seq_client *get_event_dest_client(struct snd_seq_event *event, + int filter) +{ + struct snd_seq_client *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(struct snd_seq_client *client, + struct snd_seq_event *event, + int err, int atomic, int hop) +{ + struct snd_seq_event bounce_ev; + 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. + */ +static int update_timestamp_of_queue(struct snd_seq_event *event, + int queue, int real_time) +{ + struct snd_seq_queue *q; + + 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, true); + 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 a single event; called from below and UMP converter */ +int __snd_seq_deliver_single_event(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + switch (dest->type) { + case USER_CLIENT: + if (!dest->data.user.fifo) + return 0; + return snd_seq_fifo_event_in(dest->data.user.fifo, event); + case KERNEL_CLIENT: + if (!dest_port->event_input) + return 0; + return dest_port->event_input(event, + snd_seq_ev_is_direct(event), + dest_port->private_data, + atomic, hop); + } + return 0; +} + +/* + * deliver an event to the specified destination. + * if filter is non-zero, client filter bitmap is tested. + * + * RETURN VALUE: 0 : if succeeded + * <0 : error + */ +static int snd_seq_deliver_single_event(struct snd_seq_client *client, + struct snd_seq_event *event, + int filter, int atomic, int hop) +{ + struct snd_seq_client *dest = NULL; + struct snd_seq_client_port *dest_port = NULL; + 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); + +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + if (!(dest->filter & SNDRV_SEQ_FILTER_NO_CONVERT)) { + if (snd_seq_ev_is_ump(event)) { + result = snd_seq_deliver_from_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } else if (snd_seq_client_is_ump(dest)) { + result = snd_seq_deliver_to_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } + } +#endif /* CONFIG_SND_SEQ_UMP */ + + result = __snd_seq_deliver_single_event(dest, dest_port, event, + atomic, hop); + + __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: + */ +static int __deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + struct snd_seq_client_port *src_port, + int atomic, int hop) +{ + struct snd_seq_subscribers *subs; + int err, result = 0, num_ev = 0; + union __snd_seq_event event_saved; + size_t saved_size; + struct snd_seq_port_subs_info *grp; + + /* save original event record */ + saved_size = snd_seq_event_packet_size(event); + memcpy(&event_saved, event, saved_size); + grp = &src_port->c_src; + + /* lock list */ + if (atomic) + read_lock(&grp->list_lock); + else + down_read_nested(&grp->list_mutex, hop); + list_for_each_entry(subs, &grp->list_head, src_list) { + /* both ports ready? */ + if (atomic_read(&subs->ref_count) != 2) + continue; + 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); + if (err < 0) { + /* save first error that occurs and continue */ + if (!result) + result = err; + continue; + } + num_ev++; + /* restore original event record */ + memcpy(event, &event_saved, saved_size); + } + if (atomic) + read_unlock(&grp->list_lock); + else + up_read(&grp->list_mutex); + memcpy(event, &event_saved, saved_size); + return (result < 0) ? result : num_ev; +} + +static int deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_client_port *src_port; + int ret = 0, ret2; + + src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port) { + ret = __deliver_to_subscribers(client, event, src_port, atomic, hop); + snd_seq_port_unlock(src_port); + } + + if (client->ump_endpoint_port < 0 || + event->source.port == client->ump_endpoint_port) + return ret; + + src_port = snd_seq_port_use_ptr(client, client->ump_endpoint_port); + if (!src_port) + return ret; + ret2 = __deliver_to_subscribers(client, event, src_port, atomic, hop); + snd_seq_port_unlock(src_port); + return ret2 < 0 ? ret2 : ret; +} + +/* 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. + */ +static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_event *event, + int atomic, int hop) +{ + int result; + + hop++; + if (hop >= SNDRV_SEQ_MAX_HOPS) { + pr_debug("ALSA: seq: too long delivery path (%d:%d->%d:%d)\n", + event->source.client, event->source.port, + event->dest.client, event->dest.port); + return -EMLINK; + } + + if (snd_seq_ev_is_variable(event) && + snd_BUG_ON(atomic && (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR))) + return -EINVAL; + + if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS || + event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) + result = deliver_to_subscribers(client, event, atomic, hop); + 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. + */ +int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop) +{ + struct snd_seq_client *client; + int result; + + if (snd_BUG_ON(!cell)) + return -EINVAL; + + client = snd_seq_client_use_ptr(cell->event.source.client); + if (client == NULL) { + snd_seq_cell_free(cell); /* release this cell */ + return -EINVAL; + } + + if (!snd_seq_ev_is_ump(&cell->event) && + cell->event.type == SNDRV_SEQ_EVENT_NOTE) { + /* NOTE event: + * the event cell is re-used as a NOTE-OFF event and + * enqueued again. + */ + struct snd_seq_event tmpev, *ev; + + /* 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: + cell->event.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. + */ +static int snd_seq_client_enqueue_event(struct snd_seq_client *client, + struct snd_seq_event *event, + struct file *file, int blocking, + int atomic, int hop, + struct mutex *mutexp) +{ + struct snd_seq_event_cell *cell; + 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 if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + /* check presence of source port */ + struct snd_seq_client_port *src_port = snd_seq_port_use_ptr(client, event->source.port); + 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 (!snd_seq_ev_is_ump(event) && + 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 */ + err = snd_seq_event_dup(client->pool, event, &cell, !blocking || atomic, + file, mutexp); + if (err < 0) + return err; + + /* we got a cell. enqueue it. */ + err = snd_seq_enqueue_event(cell, atomic, hop); + if (err < 0) { + snd_seq_cell_free(cell); + return err; + } + + return 0; +} + + +/* + * check validity of event type and data length. + * return non-zero if invalid. + */ +static int check_event_type_and_length(struct snd_seq_event *ev) +{ + 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: + if (! snd_seq_ev_is_direct(ev)) + 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 + */ +static ssize_t snd_seq_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct snd_seq_client *client = file->private_data; + int written = 0, len; + int err, handled; + union __snd_seq_event __event; + struct snd_seq_event *ev = &__event.legacy; + + if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT)) + return -ENXIO; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return -ENXIO; + + if (!client->accept_output || client->pool == NULL) + return -ENXIO; + + repeat: + handled = 0; + /* allocate the pool now if the pool is not allocated yet */ + mutex_lock(&client->ioctl_mutex); + if (client->pool->size > 0 && !snd_seq_write_pool_allocated(client)) { + err = snd_seq_pool_init(client->pool); + if (err < 0) + goto out; + } + + /* only process whole events */ + err = -EINVAL; + while (count >= sizeof(struct snd_seq_event)) { + /* Read in the event header from the user */ + len = sizeof(struct snd_seq_event); + if (copy_from_user(ev, buf, len)) { + err = -EFAULT; + break; + } + /* read in the rest bytes for UMP events */ + if (snd_seq_ev_is_ump(ev)) { + if (count < sizeof(struct snd_seq_ump_event)) + break; + if (copy_from_user((char *)ev + len, buf + len, + sizeof(struct snd_seq_ump_event) - len)) { + err = -EFAULT; + break; + } + len = sizeof(struct snd_seq_ump_event); + } + + ev->source.client = client->number; /* fill in client number */ + /* Check for extension data length */ + if (check_event_type_and_length(ev)) { + err = -EINVAL; + break; + } + + if (!event_is_compatible(client, ev)) { + err = -EINVAL; + break; + } + + /* check for special events */ + if (!snd_seq_ev_is_ump(ev)) { + if (ev->type == SNDRV_SEQ_EVENT_NONE) + goto __skip_event; + else if (snd_seq_ev_is_reserved(ev)) { + err = -EINVAL; + break; + } + } + + if (snd_seq_ev_is_variable(ev)) { + int extlen = ev->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 */ + ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; + ev->data.ext.ptr = (char __force *)buf + len; + len += extlen; /* increment data length */ + } else { +#ifdef CONFIG_COMPAT + if (client->convert32 && snd_seq_ev_is_varusr(ev)) + ev->data.ext.ptr = + (void __force *)compat_ptr(ev->data.raw32.d[1]); +#endif + } + + /* ok, enqueue it */ + err = snd_seq_client_enqueue_event(client, ev, file, + !(file->f_flags & O_NONBLOCK), + 0, 0, &client->ioctl_mutex); + if (err < 0) + break; + handled++; + + __skip_event: + /* Update pointers and counts */ + count -= len; + buf += len; + written += len; + + /* let's have a coffee break if too many events are queued */ + if (++handled >= 200) { + mutex_unlock(&client->ioctl_mutex); + goto repeat; + } + } + + out: + mutex_unlock(&client->ioctl_mutex); + return written ? written : err; +} + + +/* + * handle polling + */ +static __poll_t snd_seq_poll(struct file *file, poll_table * wait) +{ + struct snd_seq_client *client = file->private_data; + __poll_t mask = 0; + + /* check client structures are in place */ + if (snd_BUG_ON(!client)) + return EPOLLERR; + + 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)) + mask |= EPOLLIN | EPOLLRDNORM; + } + + 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)) + mask |= EPOLLOUT | EPOLLWRNORM; + } + + return mask; +} + + +/*-----------------------------------------------------*/ + +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_user_pversion(struct snd_seq_client *client, void *arg) +{ + client->user_pversion = *(unsigned int *)arg; + 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; +} + +/* SYSTEM_INFO ioctl() */ +static int snd_seq_ioctl_system_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_system_info *info = arg; + + memset(info, 0, sizeof(*info)); + /* fill the info fields */ + 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(); + + return 0; +} + + +/* RUNNING_MODE ioctl() */ +static int snd_seq_ioctl_running_mode(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_running_info *info = arg; + struct snd_seq_client *cptr; + int err = 0; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(info->client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + +#ifdef SNDRV_BIG_ENDIAN + if (!info->big_endian) { + err = -EINVAL; + goto __err; + } +#else + if (info->big_endian) { + err = -EINVAL; + goto __err; + } + +#endif + if (info->cpu_mode > sizeof(long)) { + err = -EINVAL; + goto __err; + } + cptr->convert32 = (info->cpu_mode < sizeof(long)); + __err: + snd_seq_client_unlock(cptr); + return err; +} + +/* CLIENT_INFO ioctl() */ +static void get_client_info(struct snd_seq_client *cptr, + struct snd_seq_client_info *info) +{ + 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->group_filter = cptr->group_filter; + info->num_ports = cptr->num_ports; + + 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; + + info->midi_version = cptr->midi_version; + memset(info->reserved, 0, sizeof(info->reserved)); +} + +static int snd_seq_ioctl_get_client_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *client_info = arg; + struct snd_seq_client *cptr; + + /* requested client number */ + cptr = snd_seq_client_use_ptr(client_info->client); + if (cptr == NULL) + return -ENOENT; /* don't change !!! */ + + get_client_info(cptr, client_info); + snd_seq_client_unlock(cptr); + + return 0; +} + + +/* CLIENT_INFO ioctl() */ +static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *client_info = arg; + + /* it is not allowed to set the info fields for an another client */ + if (client->number != client_info->client) + return -EPERM; + /* also client type must be set now */ + if (client->type != client_info->type) + return -EINVAL; + + /* check validity of midi_version field */ + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3) && + client_info->midi_version > SNDRV_SEQ_CLIENT_UMP_MIDI_2_0) + return -EINVAL; + + /* fill the info fields */ + if (client_info->name[0]) + strscpy(client->name, client_info->name, sizeof(client->name)); + + client->filter = client_info->filter; + client->event_lost = client_info->event_lost; + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) + client->midi_version = client_info->midi_version; + memcpy(client->event_filter, client_info->event_filter, 32); + client->group_filter = client_info->group_filter; + return 0; +} + + +/* + * CREATE PORT ioctl() + */ +static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client_port *port; + struct snd_seq_port_callback *callback; + int port_idx, err; + + /* it is not allowed to create the port for an another client */ + if (info->addr.client != client->number) + return -EPERM; + if (client->type == USER_CLIENT && info->kernel) + return -EINVAL; + if ((info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) && + client->ump_endpoint_port >= 0) + return -EBUSY; + + if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) + port_idx = info->addr.port; + else + port_idx = -1; + if (port_idx >= SNDRV_SEQ_ADDRESS_UNKNOWN) + return -EINVAL; + err = snd_seq_create_port(client, port_idx, &port); + if (err < 0) + return err; + + if (client->type == KERNEL_CLIENT) { + callback = info->kernel; + if (callback) { + 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; + } + } + + info->addr = port->addr; + + snd_seq_set_port_info(port, info); + if (info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) + client->ump_endpoint_port = port->addr.port; + snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port); + snd_seq_port_unlock(port); + + return 0; +} + +/* + * DELETE PORT ioctl() + */ +static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + int err; + + /* it is not allowed to remove the port for an another client */ + if (info->addr.client != client->number) + return -EPERM; + + err = snd_seq_delete_port(client, info->addr.port); + if (err >= 0) { + if (client->ump_endpoint_port == info->addr.port) + client->ump_endpoint_port = -1; + snd_seq_system_client_ev_port_exit(client->number, info->addr.port); + } + return err; +} + + +/* + * GET_PORT_INFO ioctl() (on any client) + */ +static int snd_seq_ioctl_get_port_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client *cptr; + struct snd_seq_client_port *port; + + cptr = snd_seq_client_use_ptr(info->addr.client); + if (cptr == NULL) + return -ENXIO; + + port = snd_seq_port_use_ptr(cptr, info->addr.port); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; /* don't change */ + } + + /* get port info */ + snd_seq_get_port_info(port, info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + return 0; +} + + +/* + * SET_PORT_INFO ioctl() (only ports on this/own client) + */ +static int snd_seq_ioctl_set_port_info(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client_port *port; + + if (info->addr.client != client->number) /* only set our own ports ! */ + return -EPERM; + port = snd_seq_port_use_ptr(client, info->addr.port); + if (port) { + snd_seq_set_port_info(port, info); + 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) + +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) +{ + 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, + struct snd_seq_port_subscribe *info, + int evtype) +{ + struct snd_seq_event event; + + 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 + */ +static int snd_seq_ioctl_subscribe_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result = -EINVAL; + struct snd_seq_client *receiver = NULL, *sender = NULL; + struct snd_seq_client_port *sport = NULL, *dport = NULL; + + receiver = snd_seq_client_use_ptr(subs->dest.client); + if (!receiver) + goto __end; + sender = snd_seq_client_use_ptr(subs->sender.client); + if (!sender) + goto __end; + sport = snd_seq_port_use_ptr(sender, subs->sender.port); + if (!sport) + goto __end; + dport = snd_seq_port_use_ptr(receiver, subs->dest.port); + if (!dport) + goto __end; + + result = check_subscription_permission(client, sport, dport, subs); + if (result < 0) + goto __end; + + /* connect them */ + result = snd_seq_port_connect(client, sender, sport, receiver, dport, subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + subs, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + __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 + */ +static int snd_seq_ioctl_unsubscribe_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result = -ENXIO; + struct snd_seq_client *receiver = NULL, *sender = NULL; + struct snd_seq_client_port *sport = NULL, *dport = NULL; + + receiver = snd_seq_client_use_ptr(subs->dest.client); + if (!receiver) + goto __end; + sender = snd_seq_client_use_ptr(subs->sender.client); + if (!sender) + goto __end; + sport = snd_seq_port_use_ptr(sender, subs->sender.port); + if (!sport) + goto __end; + dport = snd_seq_port_use_ptr(receiver, subs->dest.port); + if (!dport) + goto __end; + + result = check_subscription_permission(client, sport, dport, subs); + if (result < 0) + goto __end; + + result = snd_seq_port_disconnect(client, sender, sport, receiver, dport, subs); + if (! result) /* broadcast announce */ + snd_seq_client_notify_subscription(SNDRV_SEQ_ADDRESS_SUBSCRIBERS, 0, + subs, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + __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() */ +static int snd_seq_ioctl_create_queue(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = snd_seq_queue_alloc(client->number, info->locked, info->flags); + if (IS_ERR(q)) + return PTR_ERR(q); + + info->queue = q->queue; + info->locked = q->locked; + info->owner = q->owner; + + /* set queue name */ + if (!info->name[0]) + snprintf(info->name, sizeof(info->name), "Queue-%d", q->queue); + strscpy(q->name, info->name, sizeof(q->name)); + snd_use_lock_free(&q->use_lock); + + return 0; +} + +/* DELETE_QUEUE ioctl() */ +static int snd_seq_ioctl_delete_queue(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_queue_info *info = arg; + + return snd_seq_queue_delete(client->number, info->queue); +} + +/* GET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_get_queue_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = queueptr(info->queue); + if (q == NULL) + return -EINVAL; + + memset(info, 0, sizeof(*info)); + info->queue = q->queue; + info->owner = q->owner; + info->locked = q->locked; + strscpy(info->name, q->name, sizeof(info->name)); + queuefree(q); + + return 0; +} + +/* SET_QUEUE_INFO ioctl() */ +static int snd_seq_ioctl_set_queue_info(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + if (info->owner != client->number) + return -EINVAL; + + /* change owner/locked permission */ + if (snd_seq_queue_check_access(info->queue, client->number)) { + if (snd_seq_queue_set_owner(info->queue, client->number, info->locked) < 0) + return -EPERM; + if (info->locked) + snd_seq_queue_use(info->queue, client->number, 1); + } else { + return -EPERM; + } + + q = queueptr(info->queue); + if (! q) + return -EINVAL; + if (q->owner != client->number) { + queuefree(q); + return -EPERM; + } + strscpy(q->name, info->name, sizeof(q->name)); + queuefree(q); + + return 0; +} + +/* GET_NAMED_QUEUE ioctl() */ +static int snd_seq_ioctl_get_named_queue(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_info *info = arg; + struct snd_seq_queue *q; + + q = snd_seq_queue_find_name(info->name); + if (q == NULL) + return -EINVAL; + info->queue = q->queue; + info->owner = q->owner; + info->locked = q->locked; + queuefree(q); + + return 0; +} + +/* GET_QUEUE_STATUS ioctl() */ +static int snd_seq_ioctl_get_queue_status(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_status *status = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(status->queue); + if (queue == NULL) + return -EINVAL; + memset(status, 0, sizeof(*status)); + status->queue = queue->queue; + + tmr = queue->timer; + status->events = queue->tickq->cells + queue->timeq->cells; + + status->time = snd_seq_timer_get_cur_time(tmr, true); + status->tick = snd_seq_timer_get_cur_tick(tmr); + + status->running = tmr->running; + + status->flags = queue->flags; + queuefree(queue); + + return 0; +} + + +/* GET_QUEUE_TEMPO ioctl() */ +static int snd_seq_ioctl_get_queue_tempo(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_tempo *tempo = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(tempo->queue); + if (queue == NULL) + return -EINVAL; + memset(tempo, 0, sizeof(*tempo)); + tempo->queue = queue->queue; + + tmr = queue->timer; + + tempo->tempo = tmr->tempo; + tempo->ppq = tmr->ppq; + tempo->skew_value = tmr->skew; + tempo->skew_base = tmr->skew_base; + queuefree(queue); + + return 0; +} + + +/* SET_QUEUE_TEMPO ioctl() */ +int snd_seq_set_queue_tempo(int client, struct snd_seq_queue_tempo *tempo) +{ + if (!snd_seq_queue_check_access(tempo->queue, client)) + return -EPERM; + return snd_seq_queue_timer_set_tempo(tempo->queue, client, tempo); +} +EXPORT_SYMBOL(snd_seq_set_queue_tempo); + +static int snd_seq_ioctl_set_queue_tempo(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_tempo *tempo = arg; + int result; + + result = snd_seq_set_queue_tempo(client->number, tempo); + return result < 0 ? result : 0; +} + + +/* GET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_get_queue_timer(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_timer *timer = arg; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(timer->queue); + if (queue == NULL) + return -EINVAL; + + mutex_lock(&queue->timer_mutex); + tmr = queue->timer; + memset(timer, 0, sizeof(*timer)); + timer->queue = queue->queue; + + timer->type = tmr->type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + timer->u.alsa.id = tmr->alsa_id; + timer->u.alsa.resolution = tmr->preferred_resolution; + } + mutex_unlock(&queue->timer_mutex); + queuefree(queue); + + return 0; +} + + +/* SET_QUEUE_TIMER ioctl() */ +static int snd_seq_ioctl_set_queue_timer(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_timer *timer = arg; + int result = 0; + + if (timer->type != SNDRV_SEQ_TIMER_ALSA) + return -EINVAL; + + if (snd_seq_queue_check_access(timer->queue, client->number)) { + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + + q = queueptr(timer->queue); + if (q == NULL) + return -ENXIO; + mutex_lock(&q->timer_mutex); + tmr = q->timer; + snd_seq_queue_timer_close(timer->queue); + tmr->type = timer->type; + if (tmr->type == SNDRV_SEQ_TIMER_ALSA) { + tmr->alsa_id = timer->u.alsa.id; + tmr->preferred_resolution = timer->u.alsa.resolution; + } + result = snd_seq_queue_timer_open(timer->queue); + mutex_unlock(&q->timer_mutex); + queuefree(q); + } else { + return -EPERM; + } + + return result; +} + + +/* GET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_get_queue_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_client *info = arg; + int used; + + used = snd_seq_queue_is_used(info->queue, client->number); + if (used < 0) + return -EINVAL; + info->used = used; + info->client = client->number; + + return 0; +} + + +/* SET_QUEUE_CLIENT ioctl() */ +static int snd_seq_ioctl_set_queue_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_queue_client *info = arg; + int err; + + if (info->used >= 0) { + err = snd_seq_queue_use(info->queue, client->number, info->used); + if (err < 0) + return err; + } + + return snd_seq_ioctl_get_queue_client(client, arg); +} + + +/* GET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_get_client_pool(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_pool *info = arg; + struct snd_seq_client *cptr; + + cptr = snd_seq_client_use_ptr(info->client); + if (cptr == NULL) + return -ENOENT; + memset(info, 0, sizeof(*info)); + info->client = cptr->number; + info->output_pool = cptr->pool->size; + info->output_room = cptr->pool->room; + info->output_free = info->output_pool; + info->output_free = snd_seq_unused_cells(cptr->pool); + if (cptr->type == USER_CLIENT) { + info->input_pool = cptr->data.user.fifo_pool_size; + info->input_free = info->input_pool; + info->input_free = snd_seq_fifo_unused_cells(cptr->data.user.fifo); + } else { + info->input_pool = 0; + info->input_free = 0; + } + snd_seq_client_unlock(cptr); + + return 0; +} + +/* SET_CLIENT_POOL ioctl() */ +static int snd_seq_ioctl_set_client_pool(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_pool *info = arg; + int rc; + + if (client->number != info->client) + return -EINVAL; /* can't change other clients */ + + if (info->output_pool >= 1 && info->output_pool <= SNDRV_SEQ_MAX_EVENTS && + (! snd_seq_write_pool_allocated(client) || + info->output_pool != client->pool->size)) { + if (snd_seq_write_pool_allocated(client)) { + /* is the pool in use? */ + if (atomic_read(&client->pool->counter)) + return -EBUSY; + /* remove all existing cells */ + snd_seq_pool_mark_closing(client->pool); + snd_seq_pool_done(client->pool); + } + client->pool->size = info->output_pool; + rc = snd_seq_pool_init(client->pool); + if (rc < 0) + return rc; + } + if (client->type == USER_CLIENT && client->data.user.fifo != NULL && + info->input_pool >= 1 && + info->input_pool <= SNDRV_SEQ_MAX_CLIENT_EVENTS && + info->input_pool != client->data.user.fifo_pool_size) { + /* change pool size */ + rc = snd_seq_fifo_resize(client->data.user.fifo, info->input_pool); + if (rc < 0) + return rc; + client->data.user.fifo_pool_size = info->input_pool; + } + if (info->output_room >= 1 && + info->output_room <= client->pool->size) { + client->pool->room = info->output_room; + } + + return snd_seq_ioctl_get_client_pool(client, arg); +} + + +/* REMOVE_EVENTS ioctl() */ +static int snd_seq_ioctl_remove_events(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_remove_events *info = arg; + + /* + * Input mostly not implemented XXX. + */ + if (info->remove_mode & SNDRV_SEQ_REMOVE_INPUT) { + /* + * No restrictions so for a user client we can clear + * the whole fifo + */ + if (client->type == USER_CLIENT && client->data.user.fifo) + snd_seq_fifo_clear(client->data.user.fifo); + } + + if (info->remove_mode & SNDRV_SEQ_REMOVE_OUTPUT) + snd_seq_queue_remove_cells(client->number, info); + + return 0; +} + + +/* + * get subscription info + */ +static int snd_seq_ioctl_get_subscription(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_subscribe *subs = arg; + int result; + struct snd_seq_client *sender = NULL; + struct snd_seq_client_port *sport = NULL; + + result = -EINVAL; + sender = snd_seq_client_use_ptr(subs->sender.client); + if (!sender) + goto __end; + sport = snd_seq_port_use_ptr(sender, subs->sender.port); + if (!sport) + goto __end; + result = snd_seq_port_get_subscription(&sport->c_src, &subs->dest, + subs); + __end: + if (sport) + snd_seq_port_unlock(sport); + if (sender) + snd_seq_client_unlock(sender); + + return result; +} + + +/* + * get subscription info - check only its presence + */ +static int snd_seq_ioctl_query_subs(struct snd_seq_client *client, void *arg) +{ + struct snd_seq_query_subs *subs = arg; + int result = -ENXIO; + struct snd_seq_client *cptr = NULL; + struct snd_seq_client_port *port = NULL; + struct snd_seq_port_subs_info *group; + struct list_head *p; + int i; + + cptr = snd_seq_client_use_ptr(subs->root.client); + if (!cptr) + goto __end; + port = snd_seq_port_use_ptr(cptr, subs->root.port); + if (!port) + goto __end; + + switch (subs->type) { + case SNDRV_SEQ_QUERY_SUBS_READ: + group = &port->c_src; + break; + case SNDRV_SEQ_QUERY_SUBS_WRITE: + group = &port->c_dest; + break; + default: + goto __end; + } + + down_read(&group->list_mutex); + /* search for the subscriber */ + subs->num_subs = group->count; + i = 0; + result = -ENOENT; + list_for_each(p, &group->list_head) { + if (i++ == subs->index) { + /* found! */ + struct snd_seq_subscribers *s; + if (subs->type == SNDRV_SEQ_QUERY_SUBS_READ) { + s = list_entry(p, struct snd_seq_subscribers, src_list); + subs->addr = s->info.dest; + } else { + s = list_entry(p, struct snd_seq_subscribers, dest_list); + subs->addr = s->info.sender; + } + subs->flags = s->info.flags; + subs->queue = s->info.queue; + result = 0; + break; + } + } + up_read(&group->list_mutex); + + __end: + if (port) + snd_seq_port_unlock(port); + if (cptr) + snd_seq_client_unlock(cptr); + + return result; +} + + +/* + * query next client + */ +static int snd_seq_ioctl_query_next_client(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_client_info *info = arg; + struct snd_seq_client *cptr = NULL; + + /* search for next client */ + if (info->client < INT_MAX) + info->client++; + if (info->client < 0) + info->client = 0; + for (; info->client < SNDRV_SEQ_MAX_CLIENTS; info->client++) { + cptr = snd_seq_client_use_ptr(info->client); + if (cptr) + break; /* found */ + } + if (cptr == NULL) + return -ENOENT; + + get_client_info(cptr, info); + snd_seq_client_unlock(cptr); + + return 0; +} + +/* + * query next port + */ +static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client, + void *arg) +{ + struct snd_seq_port_info *info = arg; + struct snd_seq_client *cptr; + struct snd_seq_client_port *port = NULL; + + cptr = snd_seq_client_use_ptr(info->addr.client); + if (cptr == NULL) + return -ENXIO; + + /* search for next port */ + info->addr.port++; + port = snd_seq_port_query_nearest(cptr, info); + if (port == NULL) { + snd_seq_client_unlock(cptr); + return -ENOENT; + } + + /* get port info */ + info->addr = port->addr; + snd_seq_get_port_info(port, info); + snd_seq_port_unlock(port); + snd_seq_client_unlock(cptr); + + return 0; +} + +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) +#define NUM_UMP_INFOS (SNDRV_UMP_MAX_BLOCKS + 1) + +static void free_ump_info(struct snd_seq_client *client) +{ + int i; + + if (!client->ump_info) + return; + for (i = 0; i < NUM_UMP_INFOS; i++) + kfree(client->ump_info[i]); + kfree(client->ump_info); + client->ump_info = NULL; +} + +static void terminate_ump_info_strings(void *p, int type) +{ + if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) { + struct snd_ump_endpoint_info *ep = p; + ep->name[sizeof(ep->name) - 1] = 0; + } else { + struct snd_ump_block_info *bp = p; + bp->name[sizeof(bp->name) - 1] = 0; + } +} + +#ifdef CONFIG_SND_PROC_FS +static void dump_ump_info(struct snd_info_buffer *buffer, + struct snd_seq_client *client) +{ + struct snd_ump_endpoint_info *ep; + struct snd_ump_block_info *bp; + int i; + + if (!client->ump_info) + return; + ep = client->ump_info[SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT]; + if (ep && *ep->name) + snd_iprintf(buffer, " UMP Endpoint: \"%s\"\n", ep->name); + for (i = 0; i < SNDRV_UMP_MAX_BLOCKS; i++) { + bp = client->ump_info[i + 1]; + if (bp && *bp->name) { + snd_iprintf(buffer, " UMP Block %d: \"%s\" [%s]\n", + i, bp->name, + bp->active ? "Active" : "Inactive"); + snd_iprintf(buffer, " Groups: %d-%d\n", + bp->first_group + 1, + bp->first_group + bp->num_groups); + } + } +} +#endif + +/* UMP-specific ioctls -- called directly without data copy */ +static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller, + unsigned int cmd, + unsigned long arg) +{ + struct snd_seq_client_ump_info __user *argp = + (struct snd_seq_client_ump_info __user *)arg; + struct snd_seq_client *cptr; + int client, type, err = 0; + size_t size; + void *p; + + if (get_user(client, &argp->client) || get_user(type, &argp->type)) + return -EFAULT; + if (cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO && + caller->number != client) + return -EPERM; + if (type < 0 || type >= NUM_UMP_INFOS) + return -EINVAL; + if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) + size = sizeof(struct snd_ump_endpoint_info); + else + size = sizeof(struct snd_ump_block_info); + cptr = snd_seq_client_use_ptr(client); + if (!cptr) + return -ENOENT; + + mutex_lock(&cptr->ioctl_mutex); + if (!cptr->midi_version) { + err = -EBADFD; + goto error; + } + + if (cmd == SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO) { + if (!cptr->ump_info) + p = NULL; + else + p = cptr->ump_info[type]; + if (!p) { + err = -ENODEV; + goto error; + } + if (copy_to_user(argp->info, p, size)) { + err = -EFAULT; + goto error; + } + } else { + if (cptr->type != USER_CLIENT) { + err = -EBADFD; + goto error; + } + if (!cptr->ump_info) { + cptr->ump_info = kcalloc(NUM_UMP_INFOS, + sizeof(void *), GFP_KERNEL); + if (!cptr->ump_info) { + err = -ENOMEM; + goto error; + } + } + p = memdup_user(argp->info, size); + if (IS_ERR(p)) { + err = PTR_ERR(p); + goto error; + } + kfree(cptr->ump_info[type]); + terminate_ump_info_strings(p, type); + cptr->ump_info[type] = p; + } + + error: + mutex_unlock(&cptr->ioctl_mutex); + snd_seq_client_unlock(cptr); + return err; +} +#endif + +/* -------------------------------------------------------- */ + +static const struct ioctl_handler { + unsigned int cmd; + int (*func)(struct snd_seq_client *client, void *arg); +} ioctl_handlers[] = { + { SNDRV_SEQ_IOCTL_PVERSION, snd_seq_ioctl_pversion }, + { SNDRV_SEQ_IOCTL_USER_PVERSION, snd_seq_ioctl_user_pversion }, + { SNDRV_SEQ_IOCTL_CLIENT_ID, snd_seq_ioctl_client_id }, + { SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info }, + { SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, snd_seq_ioctl_get_client_info }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, snd_seq_ioctl_set_client_info }, + { SNDRV_SEQ_IOCTL_CREATE_PORT, snd_seq_ioctl_create_port }, + { SNDRV_SEQ_IOCTL_DELETE_PORT, snd_seq_ioctl_delete_port }, + { SNDRV_SEQ_IOCTL_GET_PORT_INFO, snd_seq_ioctl_get_port_info }, + { SNDRV_SEQ_IOCTL_SET_PORT_INFO, snd_seq_ioctl_set_port_info }, + { SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, snd_seq_ioctl_subscribe_port }, + { SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT, snd_seq_ioctl_unsubscribe_port }, + { SNDRV_SEQ_IOCTL_CREATE_QUEUE, snd_seq_ioctl_create_queue }, + { SNDRV_SEQ_IOCTL_DELETE_QUEUE, snd_seq_ioctl_delete_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_INFO, snd_seq_ioctl_get_queue_info }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_INFO, snd_seq_ioctl_set_queue_info }, + { SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE, snd_seq_ioctl_get_named_queue }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS, snd_seq_ioctl_get_queue_status }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO, snd_seq_ioctl_get_queue_tempo }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO, snd_seq_ioctl_set_queue_tempo }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER, snd_seq_ioctl_get_queue_timer }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER, snd_seq_ioctl_set_queue_timer }, + { SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT, snd_seq_ioctl_get_queue_client }, + { SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT, snd_seq_ioctl_set_queue_client }, + { SNDRV_SEQ_IOCTL_GET_CLIENT_POOL, snd_seq_ioctl_get_client_pool }, + { SNDRV_SEQ_IOCTL_SET_CLIENT_POOL, snd_seq_ioctl_set_client_pool }, + { SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION, snd_seq_ioctl_get_subscription }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT, snd_seq_ioctl_query_next_client }, + { SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, snd_seq_ioctl_query_next_port }, + { SNDRV_SEQ_IOCTL_REMOVE_EVENTS, snd_seq_ioctl_remove_events }, + { SNDRV_SEQ_IOCTL_QUERY_SUBS, snd_seq_ioctl_query_subs }, + { 0, NULL }, +}; + +static long snd_seq_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_seq_client *client = file->private_data; + /* To use kernel stack for ioctl data. */ + union { + int pversion; + int client_id; + struct snd_seq_system_info system_info; + struct snd_seq_running_info running_info; + struct snd_seq_client_info client_info; + struct snd_seq_port_info port_info; + struct snd_seq_port_subscribe port_subscribe; + struct snd_seq_queue_info queue_info; + struct snd_seq_queue_status queue_status; + struct snd_seq_queue_tempo tempo; + struct snd_seq_queue_timer queue_timer; + struct snd_seq_queue_client queue_client; + struct snd_seq_client_pool client_pool; + struct snd_seq_remove_events remove_events; + struct snd_seq_query_subs query_subs; + } buf; + const struct ioctl_handler *handler; + unsigned long size; + int err; + + if (snd_BUG_ON(!client)) + return -ENXIO; + +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + /* exception - handling large data */ + switch (cmd) { + case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO: + return snd_seq_ioctl_client_ump_info(client, cmd, arg); + } +#endif + + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { + if (handler->cmd == cmd) + break; + } + if (handler->cmd == 0) + return -ENOTTY; + + memset(&buf, 0, sizeof(buf)); + + /* + * All of ioctl commands for ALSA sequencer get an argument of size + * within 13 bits. We can safely pick up the size from the command. + */ + size = _IOC_SIZE(handler->cmd); + if (handler->cmd & IOC_IN) { + if (copy_from_user(&buf, (const void __user *)arg, size)) + return -EFAULT; + } + + mutex_lock(&client->ioctl_mutex); + err = handler->func(client, &buf); + mutex_unlock(&client->ioctl_mutex); + if (err >= 0) { + /* Some commands includes a bug in 'dir' field. */ + if (handler->cmd == SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT || + handler->cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_POOL || + (handler->cmd & IOC_OUT)) + if (copy_to_user((void __user *)arg, &buf, size)) + return -EFAULT; + } + + return err; +} + +#ifdef CONFIG_COMPAT +#include "seq_compat.c" +#else +#define snd_seq_ioctl_compat NULL +#endif + +/* -------------------------------------------------------- */ + + +/* exported to kernel modules */ +int snd_seq_create_kernel_client(struct snd_card *card, int client_index, + const char *name_fmt, ...) +{ + struct snd_seq_client *client; + va_list args; + + if (snd_BUG_ON(in_interrupt())) + return -EBUSY; + + if (card && client_index >= SNDRV_SEQ_CLIENTS_PER_CARD) + return -EINVAL; + if (card == NULL && client_index >= SNDRV_SEQ_GLOBAL_CLIENTS) + return -EINVAL; + + mutex_lock(®ister_mutex); + + if (card) { + client_index += SNDRV_SEQ_GLOBAL_CLIENTS + + card->number * SNDRV_SEQ_CLIENTS_PER_CARD; + if (client_index >= SNDRV_SEQ_DYNAMIC_CLIENTS_BEGIN) + client_index = -1; + } + + /* empty write queue as default */ + client = seq_create_client1(client_index, 0); + if (client == NULL) { + mutex_unlock(®ister_mutex); + return -EBUSY; /* failure code */ + } + usage_alloc(&client_usage, 1); + + client->accept_input = 1; + client->accept_output = 1; + client->data.kernel.card = card; + client->user_pversion = SNDRV_SEQ_VERSION; + + va_start(args, name_fmt); + vsnprintf(client->name, sizeof(client->name), name_fmt, args); + va_end(args); + + client->type = KERNEL_CLIENT; + mutex_unlock(®ister_mutex); + + /* make others aware this new client */ + snd_seq_system_client_ev_client_start(client->number); + + /* return client number to caller */ + return client->number; +} +EXPORT_SYMBOL(snd_seq_create_kernel_client); + +/* exported to kernel modules */ +int snd_seq_delete_kernel_client(int client) +{ + struct snd_seq_client *ptr; + + if (snd_BUG_ON(in_interrupt())) + return -EBUSY; + + ptr = clientptr(client); + if (ptr == NULL) + return -EINVAL; + + seq_free_client(ptr); + kfree(ptr); + return 0; +} +EXPORT_SYMBOL(snd_seq_delete_kernel_client); + +/* + * exported, called by kernel clients to enqueue events (w/o blocking) + * + * RETURN VALUE: zero if succeed, negative if error + */ +int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev, + struct file *file, bool blocking) +{ + struct snd_seq_client *cptr; + int result; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + + if (!snd_seq_ev_is_ump(ev)) { + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* ignore this */ + if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return -EINVAL; /* quoted events can't be enqueued */ + } + + /* fill in client number */ + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (!cptr->accept_output) { + result = -EPERM; + } else { /* send it */ + mutex_lock(&cptr->ioctl_mutex); + result = snd_seq_client_enqueue_event(cptr, ev, file, blocking, + false, 0, + &cptr->ioctl_mutex); + mutex_unlock(&cptr->ioctl_mutex); + } + + snd_seq_client_unlock(cptr); + return result; +} +EXPORT_SYMBOL(snd_seq_kernel_client_enqueue); + +/* + * exported, called by kernel clients to dispatch events directly to other + * clients, bypassing the queues. Event time-stamp will be updated. + * + * RETURN VALUE: negative = delivery failed, + * zero, or positive: the number of delivered events + */ +int snd_seq_kernel_client_dispatch(int client, struct snd_seq_event * ev, + int atomic, int hop) +{ + struct snd_seq_client *cptr; + int result; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + + /* fill in client number */ + ev->queue = SNDRV_SEQ_QUEUE_DIRECT; + ev->source.client = client; + + if (check_event_type_and_length(ev)) + return -EINVAL; + + cptr = snd_seq_client_use_ptr(client); + if (cptr == NULL) + return -EINVAL; + + if (!cptr->accept_output) + result = -EPERM; + else + result = snd_seq_deliver_event(cptr, ev, atomic, hop); + + snd_seq_client_unlock(cptr); + return result; +} +EXPORT_SYMBOL(snd_seq_kernel_client_dispatch); + +/** + * snd_seq_kernel_client_ctl - operate a command for a client with data in + * kernel space. + * @clientid: A numerical ID for a client. + * @cmd: An ioctl(2) command for ALSA sequencer operation. + * @arg: A pointer to data in kernel space. + * + * Against its name, both kernel/application client can be handled by this + * kernel API. A pointer of 'arg' argument should be in kernel space. + * + * Return: 0 at success. Negative error code at failure. + */ +int snd_seq_kernel_client_ctl(int clientid, unsigned int cmd, void *arg) +{ + const struct ioctl_handler *handler; + struct snd_seq_client *client; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { + if (handler->cmd == cmd) + return handler->func(client, arg); + } + + pr_debug("ALSA: seq unknown ioctl() 0x%x (type='%c', number=0x%02x)\n", + cmd, _IOC_TYPE(cmd), _IOC_NR(cmd)); + return -ENOTTY; +} +EXPORT_SYMBOL(snd_seq_kernel_client_ctl); + +/* exported (for OSS emulator) */ +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait) +{ + struct snd_seq_client *client; + + client = clientptr(clientid); + if (client == NULL) + return -ENXIO; + + if (! snd_seq_write_pool_allocated(client)) + return 1; + if (snd_seq_pool_poll_wait(client->pool, file, wait)) + return 1; + return 0; +} +EXPORT_SYMBOL(snd_seq_kernel_client_write_poll); + +/* get a sequencer client object; for internal use from a kernel client */ +struct snd_seq_client *snd_seq_kernel_client_get(int id) +{ + return snd_seq_client_use_ptr(id); +} +EXPORT_SYMBOL_GPL(snd_seq_kernel_client_get); + +/* put a sequencer client object; for internal use from a kernel client */ +void snd_seq_kernel_client_put(struct snd_seq_client *cptr) +{ + if (cptr) + snd_seq_client_unlock(cptr); +} +EXPORT_SYMBOL_GPL(snd_seq_kernel_client_put); + +/*---------------------------------------------------------------------------*/ + +#ifdef CONFIG_SND_PROC_FS +/* + * /proc interface + */ +static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, + struct snd_seq_port_subs_info *group, + int is_src, char *msg) +{ + struct list_head *p; + struct snd_seq_subscribers *s; + int count = 0; + + down_read(&group->list_mutex); + if (list_empty(&group->list_head)) { + up_read(&group->list_mutex); + return; + } + snd_iprintf(buffer, msg); + list_for_each(p, &group->list_head) { + if (is_src) + s = list_entry(p, struct snd_seq_subscribers, src_list); + else + s = list_entry(p, struct snd_seq_subscribers, dest_list); + if (count++) + snd_iprintf(buffer, ", "); + snd_iprintf(buffer, "%d:%d", + is_src ? s->info.dest.client : s->info.sender.client, + is_src ? s->info.dest.port : s->info.sender.port); + if (s->info.flags & SNDRV_SEQ_PORT_SUBS_TIMESTAMP) + snd_iprintf(buffer, "[%c:%d]", ((s->info.flags & SNDRV_SEQ_PORT_SUBS_TIME_REAL) ? 'r' : 't'), s->info.queue); + if (group->exclusive) + snd_iprintf(buffer, "[ex]"); + } + up_read(&group->list_mutex); + snd_iprintf(buffer, "\n"); +} + +#define FLAG_PERM_RD(perm) ((perm) & SNDRV_SEQ_PORT_CAP_READ ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_READ ? 'R' : 'r') : '-') +#define FLAG_PERM_WR(perm) ((perm) & SNDRV_SEQ_PORT_CAP_WRITE ? ((perm) & SNDRV_SEQ_PORT_CAP_SUBS_WRITE ? 'W' : 'w') : '-') +#define FLAG_PERM_EX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_NO_EXPORT ? '-' : 'e') + +#define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-') + +static const char *port_direction_name(unsigned char dir) +{ + static const char *names[4] = { + "-", "In", "Out", "In/Out" + }; + + if (dir > SNDRV_SEQ_PORT_DIR_BIDIRECTION) + return "Invalid"; + return names[dir]; +} + +static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, + struct snd_seq_client *client) +{ + struct snd_seq_client_port *p; + + mutex_lock(&client->ports_mutex); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) + continue; + snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c) [%s]\n", + p->addr.port, p->name, + FLAG_PERM_RD(p->capability), + FLAG_PERM_WR(p->capability), + FLAG_PERM_EX(p->capability), + FLAG_PERM_DUPLEX(p->capability), + port_direction_name(p->direction)); + snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: "); + snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: "); + } + mutex_unlock(&client->ports_mutex); +} + +static const char *midi_version_string(unsigned int version) +{ + switch (version) { + case SNDRV_SEQ_CLIENT_LEGACY_MIDI: + return "Legacy"; + case SNDRV_SEQ_CLIENT_UMP_MIDI_1_0: + return "UMP MIDI1"; + case SNDRV_SEQ_CLIENT_UMP_MIDI_2_0: + return "UMP MIDI2"; + default: + return "Unknown"; + } +} + +/* exported to seq_info.c */ +void snd_seq_info_clients_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int c; + struct snd_seq_client *client; + + snd_iprintf(buffer, "Client info\n"); + snd_iprintf(buffer, " cur clients : %d\n", client_usage.cur); + snd_iprintf(buffer, " peak clients : %d\n", client_usage.peak); + snd_iprintf(buffer, " max clients : %d\n", SNDRV_SEQ_MAX_CLIENTS); + snd_iprintf(buffer, "\n"); + + /* list the client table */ + for (c = 0; c < SNDRV_SEQ_MAX_CLIENTS; c++) { + client = snd_seq_client_use_ptr(c); + if (client == NULL) + continue; + if (client->type == NO_CLIENT) { + snd_seq_client_unlock(client); + continue; + } + + snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n", + c, client->name, + client->type == USER_CLIENT ? "User" : "Kernel", + midi_version_string(client->midi_version)); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + dump_ump_info(buffer, client); +#endif + snd_seq_info_dump_ports(buffer, client); + if (snd_seq_write_pool_allocated(client)) { + snd_iprintf(buffer, " Output pool :\n"); + snd_seq_info_pool(buffer, client->pool, " "); + } + if (client->type == USER_CLIENT && client->data.user.fifo && + client->data.user.fifo->pool) { + snd_iprintf(buffer, " Input pool :\n"); + snd_seq_info_pool(buffer, client->data.user.fifo->pool, " "); + } + snd_seq_client_unlock(client); + } +} +#endif /* CONFIG_SND_PROC_FS */ + +/*---------------------------------------------------------------------------*/ + + +/* + * REGISTRATION PART + */ + +static const struct file_operations snd_seq_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_seq_read, + .write = snd_seq_write, + .open = snd_seq_open, + .release = snd_seq_release, + .llseek = no_llseek, + .poll = snd_seq_poll, + .unlocked_ioctl = snd_seq_ioctl, + .compat_ioctl = snd_seq_ioctl_compat, +}; + +static struct device *seq_dev; + +/* + * register sequencer device + */ +int __init snd_sequencer_device_init(void) +{ + int err; + + err = snd_device_alloc(&seq_dev, NULL); + if (err < 0) + return err; + dev_set_name(seq_dev, "seq"); + + mutex_lock(®ister_mutex); + err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0, + &snd_seq_f_ops, NULL, seq_dev); + mutex_unlock(®ister_mutex); + if (err < 0) { + put_device(seq_dev); + return err; + } + + return 0; +} + + + +/* + * unregister sequencer device + */ +void snd_sequencer_device_done(void) +{ + snd_unregister_device(seq_dev); + put_device(seq_dev); +} diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h new file mode 100644 index 0000000000..915b101728 --- /dev/null +++ b/sound/core/seq/seq_clientmgr.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Client Manager + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_CLIENTMGR_H +#define __SND_SEQ_CLIENTMGR_H + +#include <sound/seq_kernel.h> +#include <linux/bitops.h> +#include "seq_fifo.h" +#include "seq_ports.h" +#include "seq_lock.h" + +/* client manager */ + +struct snd_seq_user_client { + struct file *file; /* file struct of client */ + /* ... */ + struct pid *owner; + + /* fifo */ + struct snd_seq_fifo *fifo; /* queue for incoming events */ + int fifo_pool_size; +}; + +struct snd_seq_kernel_client { + /* ... */ + struct snd_card *card; +}; + + +struct snd_seq_client { + snd_seq_client_type_t type; + unsigned int accept_input: 1, + accept_output: 1; + unsigned int midi_version; + unsigned int user_pversion; + char name[64]; /* client name */ + int number; /* client number */ + unsigned int filter; /* filter flags */ + DECLARE_BITMAP(event_filter, 256); + unsigned short group_filter; + snd_use_lock_t use_lock; + int event_lost; + /* ports */ + int num_ports; /* number of ports */ + struct list_head ports_list_head; + rwlock_t ports_lock; + struct mutex ports_mutex; + struct mutex ioctl_mutex; + int convert32; /* convert 32->64bit */ + int ump_endpoint_port; + + /* output pool */ + struct snd_seq_pool *pool; /* memory pool for this client */ + + union { + struct snd_seq_user_client user; + struct snd_seq_kernel_client kernel; + } data; + + /* for UMP */ + void **ump_info; +}; + +/* usage statistics */ +struct snd_seq_usage { + int cur; + int peak; +}; + + +int client_init_data(void); +int snd_sequencer_device_init(void); +void snd_sequencer_device_done(void); + +/* get locked pointer to client */ +struct snd_seq_client *snd_seq_client_use_ptr(int clientid); + +/* unlock pointer to client */ +#define snd_seq_client_unlock(client) snd_use_lock_free(&(client)->use_lock) + +/* dispatch event to client(s) */ +int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop); + +int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table *wait); +int snd_seq_client_notify_subscription(int client, int port, + struct snd_seq_port_subscribe *info, int evtype); + +int __snd_seq_deliver_single_event(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); + +/* only for OSS sequencer */ +bool snd_seq_client_ioctl_lock(int clientid); +void snd_seq_client_ioctl_unlock(int clientid); + +extern int seq_client_load[15]; + +/* for internal use between kernel sequencer clients */ +struct snd_seq_client *snd_seq_kernel_client_get(int client); +void snd_seq_kernel_client_put(struct snd_seq_client *cptr); + +static inline bool snd_seq_client_is_ump(struct snd_seq_client *c) +{ + return c->midi_version != SNDRV_SEQ_CLIENT_LEGACY_MIDI; +} + +static inline bool snd_seq_client_is_midi2(struct snd_seq_client *c) +{ + return c->midi_version == SNDRV_SEQ_CLIENT_UMP_MIDI_2_0; +} + +#endif diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c new file mode 100644 index 0000000000..1e35bf086a --- /dev/null +++ b/sound/core/seq/seq_compat.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 32bit -> 64bit ioctl wrapper for sequencer API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +/* This file included from seq.c */ + +#include <linux/compat.h> +#include <linux/slab.h> + +struct snd_seq_port_info32 { + struct snd_seq_addr addr; /* client/port numbers */ + char name[64]; /* port name */ + + u32 capability; /* port capability bits */ + u32 type; /* port type bits */ + s32 midi_channels; /* channels per MIDI port */ + s32 midi_voices; /* voices per MIDI port */ + s32 synth_voices; /* voices per SYNTH port */ + + s32 read_use; /* R/O: subscribers for output (from this port) */ + s32 write_use; /* R/O: subscribers for input (to this port) */ + + u32 kernel; /* reserved for kernel use (must be NULL) */ + u32 flags; /* misc. conditioning */ + unsigned char time_queue; /* queue # for timestamping */ + char reserved[59]; /* for future use */ +}; + +static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd, + struct snd_seq_port_info32 __user *data32) +{ + int err = -EFAULT; + struct snd_seq_port_info *data; + + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (copy_from_user(data, data32, sizeof(*data32)) || + get_user(data->flags, &data32->flags) || + get_user(data->time_queue, &data32->time_queue)) + goto error; + data->kernel = NULL; + + err = snd_seq_kernel_client_ctl(client->number, cmd, data); + if (err < 0) + goto error; + + if (copy_to_user(data32, data, sizeof(*data32)) || + put_user(data->flags, &data32->flags) || + put_user(data->time_queue, &data32->time_queue)) + err = -EFAULT; + + error: + kfree(data); + return err; +} + + + +/* + */ + +enum { + SNDRV_SEQ_IOCTL_CREATE_PORT32 = _IOWR('S', 0x20, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_DELETE_PORT32 = _IOW ('S', 0x21, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_GET_PORT_INFO32 = _IOWR('S', 0x22, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_SET_PORT_INFO32 = _IOW ('S', 0x23, struct snd_seq_port_info32), + SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32 = _IOWR('S', 0x52, struct snd_seq_port_info32), +}; + +static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct snd_seq_client *client = file->private_data; + void __user *argp = compat_ptr(arg); + + if (snd_BUG_ON(!client)) + return -ENXIO; + + switch (cmd) { + case SNDRV_SEQ_IOCTL_PVERSION: + case SNDRV_SEQ_IOCTL_USER_PVERSION: + case SNDRV_SEQ_IOCTL_CLIENT_ID: + case SNDRV_SEQ_IOCTL_SYSTEM_INFO: + case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO: + case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT: + case SNDRV_SEQ_IOCTL_CREATE_QUEUE: + case SNDRV_SEQ_IOCTL_DELETE_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_INFO: + case SNDRV_SEQ_IOCTL_GET_NAMED_QUEUE: + case SNDRV_SEQ_IOCTL_GET_QUEUE_STATUS: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TEMPO: + case SNDRV_SEQ_IOCTL_GET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_SET_QUEUE_TIMER: + case SNDRV_SEQ_IOCTL_GET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_SET_QUEUE_CLIENT: + case SNDRV_SEQ_IOCTL_GET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_SET_CLIENT_POOL: + case SNDRV_SEQ_IOCTL_REMOVE_EVENTS: + case SNDRV_SEQ_IOCTL_QUERY_SUBS: + case SNDRV_SEQ_IOCTL_GET_SUBSCRIPTION: + case SNDRV_SEQ_IOCTL_QUERY_NEXT_CLIENT: + case SNDRV_SEQ_IOCTL_RUNNING_MODE: + return snd_seq_ioctl(file, cmd, arg); + case SNDRV_SEQ_IOCTL_CREATE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, argp); + case SNDRV_SEQ_IOCTL_DELETE_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_DELETE_PORT, argp); + case SNDRV_SEQ_IOCTL_GET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_GET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_SET_PORT_INFO32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_SET_PORT_INFO, argp); + case SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT32: + return snd_seq_call_port_info_ioctl(client, SNDRV_SEQ_IOCTL_QUERY_NEXT_PORT, argp); + } + return -ENOIOCTLCMD; +} diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c new file mode 100644 index 0000000000..9308194b2d --- /dev/null +++ b/sound/core/seq/seq_dummy.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer MIDI-through client + * Copyright (c) 1999-2000 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include "seq_clientmgr.h" +#include <sound/initval.h> +#include <sound/asoundef.h> + +/* + + Sequencer MIDI-through client + + This gives a simple midi-through client. All the normal input events + are redirected to output port immediately. + The routing can be done via aconnect program in alsa-utils. + + Each client has a static client number 14 (= SNDRV_SEQ_CLIENT_DUMMY). + If you want to auto-load this module, you may add the following alias + in your /etc/conf.modules file. + + alias snd-seq-client-14 snd-seq-dummy + + The module is loaded on demand for client 14, or /proc/asound/seq/ + is accessed. If you don't need this module to be loaded, alias + snd-seq-client-14 as "off". This will help modprobe. + + The number of ports to be created can be specified via the module + parameter "ports". For example, to create four ports, add the + following option in a configuration file under /etc/modprobe.d/: + + option snd-seq-dummy ports=4 + + The model option "duplex=1" enables duplex operation to the port. + In duplex mode, a pair of ports are created instead of single port, + and events are tunneled between pair-ports. For example, input to + port A is sent to output port of another port B and vice versa. + In duplex mode, each port has DUPLEX capability. + + */ + + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA sequencer MIDI-through client"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("snd-seq-client-" __stringify(SNDRV_SEQ_CLIENT_DUMMY)); + +static int ports = 1; +static bool duplex; + +module_param(ports, int, 0444); +MODULE_PARM_DESC(ports, "number of ports to be created"); +module_param(duplex, bool, 0444); +MODULE_PARM_DESC(duplex, "create DUPLEX ports"); + +struct snd_seq_dummy_port { + int client; + int port; + int duplex; + int connect; +}; + +static int my_client = -1; + +/* + * event input callback - just redirect events to subscribers + */ +static int +dummy_input(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop) +{ + struct snd_seq_dummy_port *p; + struct snd_seq_event tmpev; + + p = private_data; + if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM || + ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return 0; /* ignore system messages */ + tmpev = *ev; + if (p->duplex) + tmpev.source.port = p->connect; + else + tmpev.source.port = p->port; + tmpev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + return snd_seq_kernel_client_dispatch(p->client, &tmpev, atomic, hop); +} + +/* + * free_private callback + */ +static void +dummy_free(void *private_data) +{ + kfree(private_data); +} + +/* + * create a port + */ +static struct snd_seq_dummy_port __init * +create_port(int idx, int type) +{ + struct snd_seq_port_info pinfo; + struct snd_seq_port_callback pcb; + struct snd_seq_dummy_port *rec; + + rec = kzalloc(sizeof(*rec), GFP_KERNEL); + if (!rec) + return NULL; + + rec->client = my_client; + rec->duplex = duplex; + rec->connect = 0; + memset(&pinfo, 0, sizeof(pinfo)); + pinfo.addr.client = my_client; + if (duplex) + sprintf(pinfo.name, "Midi Through Port-%d:%c", idx, + (type ? 'B' : 'A')); + else + sprintf(pinfo.name, "Midi Through Port-%d", idx); + pinfo.capability = SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if (duplex) + pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo.direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION; + pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_SOFTWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + memset(&pcb, 0, sizeof(pcb)); + pcb.owner = THIS_MODULE; + pcb.event_input = dummy_input; + pcb.private_free = dummy_free; + pcb.private_data = rec; + pinfo.kernel = &pcb; + if (snd_seq_kernel_client_ctl(my_client, SNDRV_SEQ_IOCTL_CREATE_PORT, &pinfo) < 0) { + kfree(rec); + return NULL; + } + rec->port = pinfo.addr.port; + return rec; +} + +/* + * register client and create ports + */ +static int __init +register_client(void) +{ + struct snd_seq_dummy_port *rec1, *rec2; + struct snd_seq_client *client; + int i; + + if (ports < 1) { + pr_err("ALSA: seq_dummy: invalid number of ports %d\n", ports); + return -EINVAL; + } + + /* create client */ + my_client = snd_seq_create_kernel_client(NULL, SNDRV_SEQ_CLIENT_DUMMY, + "Midi Through"); + if (my_client < 0) + return my_client; + + /* don't convert events but just pass-through */ + client = snd_seq_kernel_client_get(my_client); + if (!client) + return -EINVAL; + client->filter = SNDRV_SEQ_FILTER_NO_CONVERT; + snd_seq_kernel_client_put(client); + + /* create ports */ + for (i = 0; i < ports; i++) { + rec1 = create_port(i, 0); + if (rec1 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + if (duplex) { + rec2 = create_port(i, 1); + if (rec2 == NULL) { + snd_seq_delete_kernel_client(my_client); + return -ENOMEM; + } + rec1->connect = rec2->port; + rec2->connect = rec1->port; + } + } + + return 0; +} + +/* + * delete client if exists + */ +static void __exit +delete_client(void) +{ + if (my_client >= 0) + snd_seq_delete_kernel_client(my_client); +} + +/* + * Init part + */ + +static int __init alsa_seq_dummy_init(void) +{ + return register_client(); +} + +static void __exit alsa_seq_dummy_exit(void) +{ + delete_client(); +} + +module_init(alsa_seq_dummy_init) +module_exit(alsa_seq_dummy_exit) diff --git a/sound/core/seq/seq_fifo.c b/sound/core/seq/seq_fifo.c new file mode 100644 index 0000000000..f8e02e9870 --- /dev/null +++ b/sound/core/seq/seq_fifo.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <sound/core.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include "seq_fifo.h" +#include "seq_lock.h" + + +/* FIFO */ + +/* create new fifo */ +struct snd_seq_fifo *snd_seq_fifo_new(int poolsize) +{ + struct snd_seq_fifo *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + f->pool = snd_seq_pool_new(poolsize); + if (f->pool == NULL) { + kfree(f); + return NULL; + } + if (snd_seq_pool_init(f->pool) < 0) { + snd_seq_pool_delete(&f->pool); + kfree(f); + return NULL; + } + + spin_lock_init(&f->lock); + snd_use_lock_init(&f->use_lock); + init_waitqueue_head(&f->input_sleep); + atomic_set(&f->overflow, 0); + + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +void snd_seq_fifo_delete(struct snd_seq_fifo **fifo) +{ + struct snd_seq_fifo *f; + + if (snd_BUG_ON(!fifo)) + return; + f = *fifo; + if (snd_BUG_ON(!f)) + return; + *fifo = NULL; + + if (f->pool) + snd_seq_pool_mark_closing(f->pool); + + snd_seq_fifo_clear(f); + + /* wake up clients if any */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + /* release resources...*/ + /*....................*/ + + if (f->pool) { + snd_seq_pool_done(f->pool); + snd_seq_pool_delete(&f->pool); + } + + kfree(f); +} + +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f); + +/* clear queue */ +void snd_seq_fifo_clear(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + + /* clear overflow flag */ + atomic_set(&f->overflow, 0); + + snd_use_lock_sync(&f->use_lock); + spin_lock_irq(&f->lock); + /* drain the fifo */ + while ((cell = fifo_cell_out(f)) != NULL) { + snd_seq_cell_free(cell); + } + spin_unlock_irq(&f->lock); +} + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(struct snd_seq_fifo *f, + struct snd_seq_event *event) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + int err; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + snd_use_lock_use(&f->use_lock); + err = snd_seq_event_dup(f->pool, event, &cell, 1, NULL, NULL); /* always non-blocking */ + if (err < 0) { + if ((err == -ENOMEM) || (err == -EAGAIN)) + atomic_inc(&f->overflow); + snd_use_lock_free(&f->use_lock); + return err; + } + + /* append new cells to fifo */ + spin_lock_irqsave(&f->lock, flags); + if (f->tail != NULL) + f->tail->next = cell; + f->tail = cell; + if (f->head == NULL) + f->head = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + + /* wakeup client */ + if (waitqueue_active(&f->input_sleep)) + wake_up(&f->input_sleep); + + snd_use_lock_free(&f->use_lock); + + return 0; /* success */ + +} + +/* dequeue cell from fifo */ +static struct snd_seq_event_cell *fifo_cell_out(struct snd_seq_fifo *f) +{ + struct snd_seq_event_cell *cell; + + cell = f->head; + if (cell) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + return cell; +} + +/* dequeue cell from fifo and copy on user space */ +int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, + struct snd_seq_event_cell **cellp, int nonblock) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + wait_queue_entry_t wait; + + if (snd_BUG_ON(!f)) + return -EINVAL; + + *cellp = NULL; + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&f->lock, flags); + while ((cell = fifo_cell_out(f)) == NULL) { + if (nonblock) { + /* non-blocking - return immediately */ + spin_unlock_irqrestore(&f->lock, flags); + return -EAGAIN; + } + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&f->input_sleep, &wait); + spin_unlock_irqrestore(&f->lock, flags); + schedule(); + spin_lock_irqsave(&f->lock, flags); + remove_wait_queue(&f->input_sleep, &wait); + if (signal_pending(current)) { + spin_unlock_irqrestore(&f->lock, flags); + return -ERESTARTSYS; + } + } + spin_unlock_irqrestore(&f->lock, flags); + *cellp = cell; + + return 0; +} + + +void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, + struct snd_seq_event_cell *cell) +{ + unsigned long flags; + + if (cell) { + spin_lock_irqsave(&f->lock, flags); + cell->next = f->head; + f->head = cell; + if (!f->tail) + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + } +} + + +/* polling; return non-zero if queue is available */ +int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, + poll_table *wait) +{ + poll_wait(file, &f->input_sleep, wait); + return (f->cells > 0); +} + +/* change the size of pool; all old events are removed */ +int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize) +{ + struct snd_seq_pool *newpool, *oldpool; + struct snd_seq_event_cell *cell, *next, *oldhead; + + if (snd_BUG_ON(!f || !f->pool)) + return -EINVAL; + + /* allocate new pool */ + newpool = snd_seq_pool_new(poolsize); + if (newpool == NULL) + return -ENOMEM; + if (snd_seq_pool_init(newpool) < 0) { + snd_seq_pool_delete(&newpool); + return -ENOMEM; + } + + spin_lock_irq(&f->lock); + /* remember old pool */ + oldpool = f->pool; + oldhead = f->head; + /* exchange pools */ + f->pool = newpool; + f->head = NULL; + f->tail = NULL; + f->cells = 0; + /* NOTE: overflow flag is not cleared */ + spin_unlock_irq(&f->lock); + + /* close the old pool and wait until all users are gone */ + snd_seq_pool_mark_closing(oldpool); + snd_use_lock_sync(&f->use_lock); + + /* release cells in old pool */ + for (cell = oldhead; cell; cell = next) { + next = cell->next; + snd_seq_cell_free(cell); + } + snd_seq_pool_delete(&oldpool); + + return 0; +} + +/* get the number of unused cells safely */ +int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f) +{ + unsigned long flags; + int cells; + + if (!f) + return 0; + + snd_use_lock_use(&f->use_lock); + spin_lock_irqsave(&f->lock, flags); + cells = snd_seq_unused_cells(f->pool); + spin_unlock_irqrestore(&f->lock, flags); + snd_use_lock_free(&f->use_lock); + return cells; +} diff --git a/sound/core/seq/seq_fifo.h b/sound/core/seq/seq_fifo.h new file mode 100644 index 0000000000..b56a7b897c --- /dev/null +++ b/sound/core/seq/seq_fifo.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer FIFO + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_FIFO_H +#define __SND_SEQ_FIFO_H + +#include "seq_memory.h" +#include "seq_lock.h" + + +/* === FIFO === */ + +struct snd_seq_fifo { + struct snd_seq_pool *pool; /* FIFO pool */ + struct snd_seq_event_cell *head; /* pointer to head of fifo */ + struct snd_seq_event_cell *tail; /* pointer to tail of fifo */ + int cells; + spinlock_t lock; + snd_use_lock_t use_lock; + wait_queue_head_t input_sleep; + atomic_t overflow; + +}; + +/* create new fifo (constructor) */ +struct snd_seq_fifo *snd_seq_fifo_new(int poolsize); + +/* delete fifo (destructor) */ +void snd_seq_fifo_delete(struct snd_seq_fifo **f); + + +/* enqueue event to fifo */ +int snd_seq_fifo_event_in(struct snd_seq_fifo *f, struct snd_seq_event *event); + +/* lock fifo from release */ +#define snd_seq_fifo_lock(fifo) snd_use_lock_use(&(fifo)->use_lock) +#define snd_seq_fifo_unlock(fifo) snd_use_lock_free(&(fifo)->use_lock) + +/* get a cell from fifo - fifo should be locked */ +int snd_seq_fifo_cell_out(struct snd_seq_fifo *f, struct snd_seq_event_cell **cellp, int nonblock); + +/* free dequeued cell - fifo should be locked */ +void snd_seq_fifo_cell_putback(struct snd_seq_fifo *f, struct snd_seq_event_cell *cell); + +/* clean up queue */ +void snd_seq_fifo_clear(struct snd_seq_fifo *f); + +/* polling */ +int snd_seq_fifo_poll_wait(struct snd_seq_fifo *f, struct file *file, poll_table *wait); + +/* resize pool in fifo */ +int snd_seq_fifo_resize(struct snd_seq_fifo *f, int poolsize); + +/* get the number of unused cells safely */ +int snd_seq_fifo_unused_cells(struct snd_seq_fifo *f); + +#endif diff --git a/sound/core/seq/seq_info.c b/sound/core/seq/seq_info.c new file mode 100644 index 0000000000..3e9fce7bea --- /dev/null +++ b/sound/core/seq/seq_info.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer /proc interface + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <sound/core.h> + +#include "seq_info.h" +#include "seq_clientmgr.h" +#include "seq_timer.h" + +static struct snd_info_entry *queues_entry; +static struct snd_info_entry *clients_entry; +static struct snd_info_entry *timer_entry; + + +static struct snd_info_entry * __init +create_info_entry(char *name, void (*read)(struct snd_info_entry *, + struct snd_info_buffer *)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, name, snd_seq_root); + if (entry == NULL) + return NULL; + entry->content = SNDRV_INFO_CONTENT_TEXT; + entry->c.text.read = read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + return NULL; + } + return entry; +} + +void snd_seq_info_done(void) +{ + snd_info_free_entry(queues_entry); + snd_info_free_entry(clients_entry); + snd_info_free_entry(timer_entry); +} + +/* create all our /proc entries */ +int __init snd_seq_info_init(void) +{ + queues_entry = create_info_entry("queues", + snd_seq_info_queues_read); + clients_entry = create_info_entry("clients", + snd_seq_info_clients_read); + timer_entry = create_info_entry("timer", snd_seq_info_timer_read); + if (!queues_entry || !clients_entry || !timer_entry) + goto error; + return 0; + + error: + snd_seq_info_done(); + return -ENOMEM; +} diff --git a/sound/core/seq/seq_info.h b/sound/core/seq/seq_info.h new file mode 100644 index 0000000000..576cf05221 --- /dev/null +++ b/sound/core/seq/seq_info.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer /proc info + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_INFO_H +#define __SND_SEQ_INFO_H + +#include <sound/info.h> +#include <sound/seq_kernel.h> + +void snd_seq_info_clients_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +void snd_seq_info_timer_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +void snd_seq_info_queues_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); + + +#ifdef CONFIG_SND_PROC_FS +int snd_seq_info_init(void); +void snd_seq_info_done(void); +#else +static inline int snd_seq_info_init(void) { return 0; } +static inline void snd_seq_info_done(void) {} +#endif + +#endif diff --git a/sound/core/seq/seq_lock.c b/sound/core/seq/seq_lock.c new file mode 100644 index 0000000000..48b4ffb4b7 --- /dev/null +++ b/sound/core/seq/seq_lock.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Do sleep inside a spin-lock + * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/export.h> +#include <sound/core.h> +#include "seq_lock.h" + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lockp, const char *file, int line) +{ + int warn_count = 5 * HZ; + + if (atomic_read(lockp) < 0) { + pr_warn("ALSA: seq_lock: lock trouble [counter = %d] in %s:%d\n", atomic_read(lockp), file, line); + return; + } + while (atomic_read(lockp) > 0) { + if (warn_count-- == 0) + pr_warn("ALSA: seq_lock: waiting [%d left] in %s:%d\n", atomic_read(lockp), file, line); + schedule_timeout_uninterruptible(1); + } +} +EXPORT_SYMBOL(snd_use_lock_sync_helper); diff --git a/sound/core/seq/seq_lock.h b/sound/core/seq/seq_lock.h new file mode 100644 index 0000000000..a973860ebc --- /dev/null +++ b/sound/core/seq/seq_lock.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __SND_SEQ_LOCK_H +#define __SND_SEQ_LOCK_H + +#include <linux/sched.h> + +typedef atomic_t snd_use_lock_t; + +/* initialize lock */ +#define snd_use_lock_init(lockp) atomic_set(lockp, 0) + +/* increment lock */ +#define snd_use_lock_use(lockp) atomic_inc(lockp) + +/* release lock */ +#define snd_use_lock_free(lockp) atomic_dec(lockp) + +/* wait until all locks are released */ +void snd_use_lock_sync_helper(snd_use_lock_t *lock, const char *file, int line); +#define snd_use_lock_sync(lockp) snd_use_lock_sync_helper(lockp, __BASE_FILE__, __LINE__) + +#endif /* __SND_SEQ_LOCK_H */ diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c new file mode 100644 index 0000000000..b603bb93f8 --- /dev/null +++ b/sound/core/seq/seq_memory.c @@ -0,0 +1,570 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + * 2000 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> +#include <linux/mm.h> +#include <sound/core.h> + +#include <sound/seq_kernel.h> +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_info.h" +#include "seq_lock.h" + +static inline int snd_seq_pool_available(struct snd_seq_pool *pool) +{ + return pool->total_elements - atomic_read(&pool->counter); +} + +static inline int snd_seq_output_ok(struct snd_seq_pool *pool) +{ + return snd_seq_pool_available(pool) >= pool->room; +} + +/* + * Variable length event: + * The event like sysex uses variable length type. + * The external data may be stored in three different formats. + * 1) kernel space + * This is the normal case. + * ext.data.len = length + * ext.data.ptr = buffer pointer + * 2) user space + * When an event is generated via read(), the external data is + * kept in user space until expanded. + * ext.data.len = length | SNDRV_SEQ_EXT_USRPTR + * ext.data.ptr = userspace pointer + * 3) chained cells + * When the variable length event is enqueued (in prioq or fifo), + * the external data is decomposed to several cells. + * ext.data.len = length | SNDRV_SEQ_EXT_CHAINED + * ext.data.ptr = the additiona cell head + * -> cell.next -> cell.next -> .. + */ + +/* + * exported: + * call dump function to expand external data. + */ + +static int get_var_len(const struct snd_seq_event *event) +{ + if ((event->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + return -EINVAL; + + return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; +} + +static int dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data, + int offset, int maxlen) +{ + int len, err; + struct snd_seq_event_cell *cell; + + len = get_var_len(event); + if (len <= 0) + return len; + if (len <= offset) + return 0; + if (maxlen && len > offset + maxlen) + len = offset + maxlen; + + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + char buf[32]; + char __user *curptr = (char __force __user *)event->data.ext.ptr; + curptr += offset; + len -= offset; + while (len > 0) { + int size = sizeof(buf); + if (len < size) + size = len; + if (copy_from_user(buf, curptr, size)) + return -EFAULT; + err = func(private_data, buf, size); + if (err < 0) + return err; + curptr += size; + len -= size; + } + return 0; + } + if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) + return func(private_data, event->data.ext.ptr + offset, + len - offset); + + cell = (struct snd_seq_event_cell *)event->data.ext.ptr; + for (; len > 0 && cell; cell = cell->next) { + int size = sizeof(struct snd_seq_event); + char *curptr = (char *)&cell->event; + + if (offset >= size) { + offset -= size; + len -= size; + continue; + } + if (len < size) + size = len; + err = func(private_data, curptr + offset, size - offset); + if (err < 0) + return err; + offset = 0; + len -= size; + } + return 0; +} + +int snd_seq_dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data) +{ + return dump_var_event(event, func, private_data, 0, 0); +} +EXPORT_SYMBOL(snd_seq_dump_var_event); + + +/* + * exported: + * expand the variable length event to linear buffer space. + */ + +static int seq_copy_in_kernel(void *ptr, void *src, int size) +{ + char **bufptr = ptr; + + memcpy(*bufptr, src, size); + *bufptr += size; + return 0; +} + +static int seq_copy_in_user(void *ptr, void *src, int size) +{ + char __user **bufptr = ptr; + + if (copy_to_user(*bufptr, src, size)) + return -EFAULT; + *bufptr += size; + return 0; +} + +static int expand_var_event(const struct snd_seq_event *event, + int offset, int size, char *buf, bool in_kernel) +{ + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + if (! in_kernel) + return -EINVAL; + if (copy_from_user(buf, + (char __force __user *)event->data.ext.ptr + offset, + size)) + return -EFAULT; + return 0; + } + return dump_var_event(event, + in_kernel ? seq_copy_in_kernel : seq_copy_in_user, + &buf, offset, size); +} + +int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, + int in_kernel, int size_aligned) +{ + int len, newlen, err; + + len = get_var_len(event); + if (len < 0) + return len; + newlen = len; + if (size_aligned > 0) + newlen = roundup(len, size_aligned); + if (count < newlen) + return -EAGAIN; + err = expand_var_event(event, 0, len, buf, in_kernel); + if (err < 0) + return err; + if (len != newlen) { + if (in_kernel) + memset(buf + len, 0, newlen - len); + else if (clear_user((__force void __user *)buf + len, + newlen - len)) + return -EFAULT; + } + return newlen; +} +EXPORT_SYMBOL(snd_seq_expand_var_event); + +int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, + char *buf, int offset) +{ + int len, err; + + len = get_var_len(event); + if (len < 0) + return len; + if (len <= offset) + return 0; + len -= offset; + if (len > count) + len = count; + err = expand_var_event(event, offset, count, buf, true); + if (err < 0) + return err; + return len; +} +EXPORT_SYMBOL_GPL(snd_seq_expand_var_event_at); + +/* + * release this cell, free extended data if available + */ + +static inline void free_cell(struct snd_seq_pool *pool, + struct snd_seq_event_cell *cell) +{ + cell->next = pool->free; + pool->free = cell; + atomic_dec(&pool->counter); +} + +void snd_seq_cell_free(struct snd_seq_event_cell * cell) +{ + unsigned long flags; + struct snd_seq_pool *pool; + + if (snd_BUG_ON(!cell)) + return; + pool = cell->pool; + if (snd_BUG_ON(!pool)) + return; + + spin_lock_irqsave(&pool->lock, flags); + free_cell(pool, cell); + if (snd_seq_ev_is_variable(&cell->event)) { + if (cell->event.data.ext.len & SNDRV_SEQ_EXT_CHAINED) { + struct snd_seq_event_cell *curp, *nextptr; + curp = cell->event.data.ext.ptr; + for (; curp; curp = nextptr) { + nextptr = curp->next; + curp->next = pool->free; + free_cell(pool, curp); + } + } + } + if (waitqueue_active(&pool->output_sleep)) { + /* has enough space now? */ + if (snd_seq_output_ok(pool)) + wake_up(&pool->output_sleep); + } + spin_unlock_irqrestore(&pool->lock, flags); +} + + +/* + * allocate an event cell. + */ +static int snd_seq_cell_alloc(struct snd_seq_pool *pool, + struct snd_seq_event_cell **cellp, + int nonblock, struct file *file, + struct mutex *mutexp) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + int err = -EAGAIN; + wait_queue_entry_t wait; + + if (pool == NULL) + return -EINVAL; + + *cellp = NULL; + + init_waitqueue_entry(&wait, current); + spin_lock_irqsave(&pool->lock, flags); + if (pool->ptr == NULL) { /* not initialized */ + pr_debug("ALSA: seq: pool is not initialized\n"); + err = -EINVAL; + goto __error; + } + while (pool->free == NULL && ! nonblock && ! pool->closing) { + + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&pool->output_sleep, &wait); + spin_unlock_irqrestore(&pool->lock, flags); + if (mutexp) + mutex_unlock(mutexp); + schedule(); + if (mutexp) + mutex_lock(mutexp); + spin_lock_irqsave(&pool->lock, flags); + remove_wait_queue(&pool->output_sleep, &wait); + /* interrupted? */ + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto __error; + } + } + if (pool->closing) { /* closing.. */ + err = -ENOMEM; + goto __error; + } + + cell = pool->free; + if (cell) { + int used; + pool->free = cell->next; + atomic_inc(&pool->counter); + used = atomic_read(&pool->counter); + if (pool->max_used < used) + pool->max_used = used; + pool->event_alloc_success++; + /* clear cell pointers */ + cell->next = NULL; + err = 0; + } else + pool->event_alloc_failures++; + *cellp = cell; + +__error: + spin_unlock_irqrestore(&pool->lock, flags); + return err; +} + + +/* + * duplicate the event to a cell. + * if the event has external data, the data is decomposed to additional + * cells. + */ +int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, + struct snd_seq_event_cell **cellp, int nonblock, + struct file *file, struct mutex *mutexp) +{ + int ncells, err; + unsigned int extlen; + struct snd_seq_event_cell *cell; + int size; + + *cellp = NULL; + + ncells = 0; + extlen = 0; + if (snd_seq_ev_is_variable(event)) { + extlen = event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; + ncells = DIV_ROUND_UP(extlen, sizeof(struct snd_seq_event)); + } + if (ncells >= pool->total_elements) + return -ENOMEM; + + err = snd_seq_cell_alloc(pool, &cell, nonblock, file, mutexp); + if (err < 0) + return err; + + /* copy the event */ + size = snd_seq_event_packet_size(event); + memcpy(&cell->ump, event, size); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + if (size < sizeof(cell->event)) + cell->ump.raw.extra = 0; +#endif + + /* decompose */ + if (snd_seq_ev_is_variable(event)) { + int len = extlen; + int is_chained = event->data.ext.len & SNDRV_SEQ_EXT_CHAINED; + int is_usrptr = event->data.ext.len & SNDRV_SEQ_EXT_USRPTR; + struct snd_seq_event_cell *src, *tmp, *tail; + char *buf; + + cell->event.data.ext.len = extlen | SNDRV_SEQ_EXT_CHAINED; + cell->event.data.ext.ptr = NULL; + + src = (struct snd_seq_event_cell *)event->data.ext.ptr; + buf = (char *)event->data.ext.ptr; + tail = NULL; + + while (ncells-- > 0) { + size = sizeof(struct snd_seq_event); + if (len < size) + size = len; + err = snd_seq_cell_alloc(pool, &tmp, nonblock, file, + mutexp); + if (err < 0) + goto __error; + if (cell->event.data.ext.ptr == NULL) + cell->event.data.ext.ptr = tmp; + if (tail) + tail->next = tmp; + tail = tmp; + /* copy chunk */ + if (is_chained && src) { + tmp->event = src->event; + src = src->next; + } else if (is_usrptr) { + if (copy_from_user(&tmp->event, (char __force __user *)buf, size)) { + err = -EFAULT; + goto __error; + } + } else { + memcpy(&tmp->event, buf, size); + } + buf += size; + len -= size; + } + } + + *cellp = cell; + return 0; + +__error: + snd_seq_cell_free(cell); + return err; +} + + +/* poll wait */ +int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, + poll_table *wait) +{ + poll_wait(file, &pool->output_sleep, wait); + return snd_seq_output_ok(pool); +} + + +/* allocate room specified number of events */ +int snd_seq_pool_init(struct snd_seq_pool *pool) +{ + int cell; + struct snd_seq_event_cell *cellptr; + + if (snd_BUG_ON(!pool)) + return -EINVAL; + + cellptr = kvmalloc_array(sizeof(struct snd_seq_event_cell), pool->size, + GFP_KERNEL); + if (!cellptr) + return -ENOMEM; + + /* add new cells to the free cell list */ + spin_lock_irq(&pool->lock); + if (pool->ptr) { + spin_unlock_irq(&pool->lock); + kvfree(cellptr); + return 0; + } + + pool->ptr = cellptr; + pool->free = NULL; + + for (cell = 0; cell < pool->size; cell++) { + cellptr = pool->ptr + cell; + cellptr->pool = pool; + cellptr->next = pool->free; + pool->free = cellptr; + } + pool->room = (pool->size + 1) / 2; + + /* init statistics */ + pool->max_used = 0; + pool->total_elements = pool->size; + spin_unlock_irq(&pool->lock); + return 0; +} + +/* refuse the further insertion to the pool */ +void snd_seq_pool_mark_closing(struct snd_seq_pool *pool) +{ + unsigned long flags; + + if (snd_BUG_ON(!pool)) + return; + spin_lock_irqsave(&pool->lock, flags); + pool->closing = 1; + spin_unlock_irqrestore(&pool->lock, flags); +} + +/* remove events */ +int snd_seq_pool_done(struct snd_seq_pool *pool) +{ + struct snd_seq_event_cell *ptr; + + if (snd_BUG_ON(!pool)) + return -EINVAL; + + /* wait for closing all threads */ + if (waitqueue_active(&pool->output_sleep)) + wake_up(&pool->output_sleep); + + while (atomic_read(&pool->counter) > 0) + schedule_timeout_uninterruptible(1); + + /* release all resources */ + spin_lock_irq(&pool->lock); + ptr = pool->ptr; + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + spin_unlock_irq(&pool->lock); + + kvfree(ptr); + + spin_lock_irq(&pool->lock); + pool->closing = 0; + spin_unlock_irq(&pool->lock); + + return 0; +} + + +/* init new memory pool */ +struct snd_seq_pool *snd_seq_pool_new(int poolsize) +{ + struct snd_seq_pool *pool; + + /* create pool block */ + pool = kzalloc(sizeof(*pool), GFP_KERNEL); + if (!pool) + return NULL; + spin_lock_init(&pool->lock); + pool->ptr = NULL; + pool->free = NULL; + pool->total_elements = 0; + atomic_set(&pool->counter, 0); + pool->closing = 0; + init_waitqueue_head(&pool->output_sleep); + + pool->size = poolsize; + + /* init statistics */ + pool->max_used = 0; + return pool; +} + +/* remove memory pool */ +int snd_seq_pool_delete(struct snd_seq_pool **ppool) +{ + struct snd_seq_pool *pool = *ppool; + + *ppool = NULL; + if (pool == NULL) + return 0; + snd_seq_pool_mark_closing(pool); + snd_seq_pool_done(pool); + kfree(pool); + return 0; +} + +/* exported to seq_clientmgr.c */ +void snd_seq_info_pool(struct snd_info_buffer *buffer, + struct snd_seq_pool *pool, char *space) +{ + if (pool == NULL) + return; + snd_iprintf(buffer, "%sPool size : %d\n", space, pool->total_elements); + snd_iprintf(buffer, "%sCells in use : %d\n", space, atomic_read(&pool->counter)); + snd_iprintf(buffer, "%sPeak cells in use : %d\n", space, pool->max_used); + snd_iprintf(buffer, "%sAlloc success : %d\n", space, pool->event_alloc_success); + snd_iprintf(buffer, "%sAlloc failures : %d\n", space, pool->event_alloc_failures); +} diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h new file mode 100644 index 0000000000..7f7a2c0b18 --- /dev/null +++ b/sound/core/seq/seq_memory.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Memory Manager + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_MEMORYMGR_H +#define __SND_SEQ_MEMORYMGR_H + +#include <sound/seq_kernel.h> +#include <linux/poll.h> + +struct snd_info_buffer; + +/* aliasing for legacy and UMP event packet handling */ +union __snd_seq_event { + struct snd_seq_event legacy; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + struct snd_seq_ump_event ump; +#endif + struct { + struct snd_seq_event event; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + u32 extra; +#endif + } __packed raw; +}; + +/* container for sequencer event (internal use) */ +struct snd_seq_event_cell { + union { + struct snd_seq_event event; + union __snd_seq_event ump; + }; + struct snd_seq_pool *pool; /* used pool */ + struct snd_seq_event_cell *next; /* next cell */ +}; + +/* design note: the pool is a contiguous block of memory, if we dynamicly + want to add additional cells to the pool be better store this in another + pool as we need to know the base address of the pool when releasing + memory. */ + +struct snd_seq_pool { + struct snd_seq_event_cell *ptr; /* pointer to first event chunk */ + struct snd_seq_event_cell *free; /* pointer to the head of the free list */ + + int total_elements; /* pool size actually allocated */ + atomic_t counter; /* cells free */ + + int size; /* pool size to be allocated */ + int room; /* watermark for sleep/wakeup */ + + int closing; + + /* statistics */ + int max_used; + int event_alloc_nopool; + int event_alloc_failures; + int event_alloc_success; + + /* Write locking */ + wait_queue_head_t output_sleep; + + /* Pool lock */ + spinlock_t lock; +}; + +void snd_seq_cell_free(struct snd_seq_event_cell *cell); + +int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, + struct snd_seq_event_cell **cellp, int nonblock, + struct file *file, struct mutex *mutexp); + +/* return number of unused (free) cells */ +static inline int snd_seq_unused_cells(struct snd_seq_pool *pool) +{ + return pool ? pool->total_elements - atomic_read(&pool->counter) : 0; +} + +/* return total number of allocated cells */ +static inline int snd_seq_total_cells(struct snd_seq_pool *pool) +{ + return pool ? pool->total_elements : 0; +} + +/* init pool - allocate events */ +int snd_seq_pool_init(struct snd_seq_pool *pool); + +/* done pool - free events */ +void snd_seq_pool_mark_closing(struct snd_seq_pool *pool); +int snd_seq_pool_done(struct snd_seq_pool *pool); + +/* create pool */ +struct snd_seq_pool *snd_seq_pool_new(int poolsize); + +/* remove pool */ +int snd_seq_pool_delete(struct snd_seq_pool **pool); + +/* polling */ +int snd_seq_pool_poll_wait(struct snd_seq_pool *pool, struct file *file, poll_table *wait); + +void snd_seq_info_pool(struct snd_info_buffer *buffer, + struct snd_seq_pool *pool, char *space); + +#endif diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c new file mode 100644 index 0000000000..18320a248a --- /dev/null +++ b/sound/core/seq/seq_midi.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Generic MIDI synth driver for ALSA sequencer + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + */ + +/* +Possible options for midisynth module: + - automatic opening of midi ports on first received event or subscription + (close will be performed when client leaves) +*/ + + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/seq_kernel.h> +#include <sound/seq_device.h> +#include <sound/seq_midi_event.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Frank van de Pol <fvdpol@coil.demon.nl>, Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI synth."); +MODULE_LICENSE("GPL"); +static int output_buffer_size = PAGE_SIZE; +module_param(output_buffer_size, int, 0644); +MODULE_PARM_DESC(output_buffer_size, "Output buffer size in bytes."); +static int input_buffer_size = PAGE_SIZE; +module_param(input_buffer_size, int, 0644); +MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes."); + +/* data for this midi synth driver */ +struct seq_midisynth { + struct snd_card *card; + struct snd_rawmidi *rmidi; + int device; + int subdevice; + struct snd_rawmidi_file input_rfile; + struct snd_rawmidi_file output_rfile; + int seq_client; + int seq_port; + struct snd_midi_event *parser; +}; + +struct seq_midisynth_client { + int seq_client; + int num_ports; + int ports_per_device[SNDRV_RAWMIDI_DEVICES]; + struct seq_midisynth *ports[SNDRV_RAWMIDI_DEVICES]; +}; + +static struct seq_midisynth_client *synths[SNDRV_CARDS]; +static DEFINE_MUTEX(register_mutex); + +/* handle rawmidi input event (MIDI v1.0 stream) */ +static void snd_midi_input_event(struct snd_rawmidi_substream *substream) +{ + struct snd_rawmidi_runtime *runtime; + struct seq_midisynth *msynth; + struct snd_seq_event ev; + char buf[16], *pbuf; + long res; + + if (substream == NULL) + return; + runtime = substream->runtime; + msynth = runtime->private_data; + if (msynth == NULL) + return; + memset(&ev, 0, sizeof(ev)); + while (runtime->avail > 0) { + res = snd_rawmidi_kernel_read(substream, buf, sizeof(buf)); + if (res <= 0) + continue; + if (msynth->parser == NULL) + continue; + pbuf = buf; + while (res-- > 0) { + if (!snd_midi_event_encode_byte(msynth->parser, + *pbuf++, &ev)) + continue; + ev.source.port = msynth->seq_port; + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(msynth->seq_client, &ev, 1, 0); + /* clear event and reset header */ + memset(&ev, 0, sizeof(ev)); + } + } +} + +static int dump_midi(struct snd_rawmidi_substream *substream, const char *buf, int count) +{ + struct snd_rawmidi_runtime *runtime; + int tmp; + + if (snd_BUG_ON(!substream || !buf)) + return -EINVAL; + runtime = substream->runtime; + tmp = runtime->avail; + if (tmp < count) { + if (printk_ratelimit()) + pr_err("ALSA: seq_midi: MIDI output buffer overrun\n"); + return -ENOMEM; + } + if (snd_rawmidi_kernel_write(substream, buf, count) < count) + return -EINVAL; + return 0; +} + +static int event_process_midi(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct seq_midisynth *msynth = private_data; + unsigned char msg[10]; /* buffer for constructing midi messages */ + struct snd_rawmidi_substream *substream; + int len; + + if (snd_BUG_ON(!msynth)) + return -EINVAL; + substream = msynth->output_rfile.output; + if (substream == NULL) + return -ENODEV; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { /* special case, to save space */ + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + /* invalid event */ + pr_debug("ALSA: seq_midi: invalid sysex event flags = 0x%x\n", ev->flags); + return 0; + } + snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)dump_midi, substream); + snd_midi_event_reset_decode(msynth->parser); + } else { + if (msynth->parser == NULL) + return -EIO; + len = snd_midi_event_decode(msynth->parser, msg, sizeof(msg), ev); + if (len < 0) + return 0; + if (dump_midi(substream, msg, len) < 0) + snd_midi_event_reset_decode(msynth->parser); + } + return 0; +} + + +static int snd_seq_midisynth_new(struct seq_midisynth *msynth, + struct snd_card *card, + int device, + int subdevice) +{ + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &msynth->parser) < 0) + return -ENOMEM; + msynth->card = card; + msynth->device = device; + msynth->subdevice = subdevice; + return 0; +} + +/* open associated midi device for input */ +static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_runtime *runtime; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, + SNDRV_RAWMIDI_LFLG_INPUT, + &msynth->input_rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi input open failed!!!\n"); + return err; + } + runtime = msynth->input_rfile.input->runtime; + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = input_buffer_size; + err = snd_rawmidi_input_params(msynth->input_rfile.input, ¶ms); + if (err < 0) { + snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; + } + snd_midi_event_reset_encode(msynth->parser); + runtime->event = snd_midi_input_event; + runtime->private_data = msynth; + snd_rawmidi_kernel_read(msynth->input_rfile.input, NULL, 0); + return 0; +} + +/* close associated midi device for input */ +static int midisynth_unsubscribe(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + + if (snd_BUG_ON(!msynth->input_rfile.input)) + return -EINVAL; + err = snd_rawmidi_kernel_release(&msynth->input_rfile); + return err; +} + +/* open associated midi device for output */ +static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info) +{ + int err; + struct seq_midisynth *msynth = private_data; + struct snd_rawmidi_params params; + + /* open midi port */ + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, + SNDRV_RAWMIDI_LFLG_OUTPUT, + &msynth->output_rfile); + if (err < 0) { + pr_debug("ALSA: seq_midi: midi output open failed!!!\n"); + return err; + } + memset(¶ms, 0, sizeof(params)); + params.avail_min = 1; + params.buffer_size = output_buffer_size; + params.no_active_sensing = 1; + err = snd_rawmidi_output_params(msynth->output_rfile.output, ¶ms); + if (err < 0) { + snd_rawmidi_kernel_release(&msynth->output_rfile); + return err; + } + snd_midi_event_reset_decode(msynth->parser); + return 0; +} + +/* close associated midi device for output */ +static int midisynth_unuse(void *private_data, struct snd_seq_port_subscribe *info) +{ + struct seq_midisynth *msynth = private_data; + + if (snd_BUG_ON(!msynth->output_rfile.output)) + return -EINVAL; + snd_rawmidi_drain_output(msynth->output_rfile.output); + return snd_rawmidi_kernel_release(&msynth->output_rfile); +} + +/* delete given midi synth port */ +static void snd_seq_midisynth_delete(struct seq_midisynth *msynth) +{ + if (msynth == NULL) + return; + + if (msynth->seq_client > 0) { + /* delete port */ + snd_seq_event_port_detach(msynth->seq_client, msynth->seq_port); + } + + snd_midi_event_free(msynth->parser); +} + +/* register new midi synth port */ +static int +snd_seq_midisynth_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct seq_midisynth_client *client; + struct seq_midisynth *msynth, *ms; + struct snd_seq_port_info *port; + struct snd_rawmidi_info *info; + struct snd_rawmidi *rmidi = dev->private_data; + int newclient = 0; + unsigned int p, ports; + struct snd_seq_port_callback pcallbacks; + struct snd_card *card = dev->card; + int device = dev->device; + unsigned int input_count = 0, output_count = 0; + + if (snd_BUG_ON(!card || device < 0 || device >= SNDRV_RAWMIDI_DEVICES)) + return -EINVAL; + info = kmalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + info->device = device; + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + info->subdevice = 0; + if (snd_rawmidi_info_select(card, info) >= 0) + output_count = info->subdevices_count; + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + if (snd_rawmidi_info_select(card, info) >= 0) { + input_count = info->subdevices_count; + } + ports = output_count; + if (ports < input_count) + ports = input_count; + if (ports == 0) { + kfree(info); + return -ENODEV; + } + if (ports > (256 / SNDRV_RAWMIDI_DEVICES)) + ports = 256 / SNDRV_RAWMIDI_DEVICES; + + mutex_lock(®ister_mutex); + client = synths[card->number]; + if (client == NULL) { + newclient = 1; + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (client == NULL) { + mutex_unlock(®ister_mutex); + kfree(info); + return -ENOMEM; + } + client->seq_client = + snd_seq_create_kernel_client( + card, 0, "%s", card->shortname[0] ? + (const char *)card->shortname : "External MIDI"); + if (client->seq_client < 0) { + kfree(client); + mutex_unlock(®ister_mutex); + kfree(info); + return -ENOMEM; + } + } + + msynth = kcalloc(ports, sizeof(struct seq_midisynth), GFP_KERNEL); + port = kmalloc(sizeof(*port), GFP_KERNEL); + if (msynth == NULL || port == NULL) + goto __nomem; + + for (p = 0; p < ports; p++) { + ms = &msynth[p]; + ms->rmidi = rmidi; + + if (snd_seq_midisynth_new(ms, card, device, p) < 0) + goto __nomem; + + /* declare port */ + memset(port, 0, sizeof(*port)); + port->addr.client = client->seq_client; + port->addr.port = device * (256 / SNDRV_RAWMIDI_DEVICES) + p; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + memset(info, 0, sizeof(*info)); + info->device = device; + if (p < output_count) + info->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; + else + info->stream = SNDRV_RAWMIDI_STREAM_INPUT; + info->subdevice = p; + if (snd_rawmidi_info_select(card, info) >= 0) + strcpy(port->name, info->subname); + if (! port->name[0]) { + if (info->name[0]) { + if (ports > 1) + scnprintf(port->name, sizeof(port->name), "%s-%u", info->name, p); + else + scnprintf(port->name, sizeof(port->name), "%s", info->name); + } else { + /* last resort */ + if (ports > 1) + sprintf(port->name, "MIDI %d-%d-%u", card->number, device, p); + else + sprintf(port->name, "MIDI %d-%d", card->number, device); + } + } + if ((info->flags & SNDRV_RAWMIDI_INFO_OUTPUT) && p < output_count) + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if ((info->flags & SNDRV_RAWMIDI_INFO_INPUT) && p < input_count) + port->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) && + info->flags & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + if (port->capability & SNDRV_SEQ_PORT_CAP_READ) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (port->capability & SNDRV_SEQ_PORT_CAP_WRITE) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_HARDWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = ms; + pcallbacks.subscribe = midisynth_subscribe; + pcallbacks.unsubscribe = midisynth_unsubscribe; + pcallbacks.use = midisynth_use; + pcallbacks.unuse = midisynth_unuse; + pcallbacks.event_input = event_process_midi; + port->kernel = &pcallbacks; + if (rmidi->ops && rmidi->ops->get_port_info) + rmidi->ops->get_port_info(rmidi, p, port); + if (snd_seq_kernel_client_ctl(client->seq_client, SNDRV_SEQ_IOCTL_CREATE_PORT, port)<0) + goto __nomem; + ms->seq_client = client->seq_client; + ms->seq_port = port->addr.port; + } + client->ports_per_device[device] = ports; + client->ports[device] = msynth; + client->num_ports++; + if (newclient) + synths[card->number] = client; + mutex_unlock(®ister_mutex); + kfree(info); + kfree(port); + return 0; /* success */ + + __nomem: + if (msynth != NULL) { + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + } + if (newclient) { + snd_seq_delete_kernel_client(client->seq_client); + kfree(client); + } + kfree(info); + kfree(port); + mutex_unlock(®ister_mutex); + return -ENOMEM; +} + +/* release midi synth port */ +static int +snd_seq_midisynth_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct seq_midisynth_client *client; + struct seq_midisynth *msynth; + struct snd_card *card = dev->card; + int device = dev->device, p, ports; + + mutex_lock(®ister_mutex); + client = synths[card->number]; + if (client == NULL || client->ports[device] == NULL) { + mutex_unlock(®ister_mutex); + return -ENODEV; + } + ports = client->ports_per_device[device]; + client->ports_per_device[device] = 0; + msynth = client->ports[device]; + client->ports[device] = NULL; + for (p = 0; p < ports; p++) + snd_seq_midisynth_delete(&msynth[p]); + kfree(msynth); + client->num_ports--; + if (client->num_ports <= 0) { + snd_seq_delete_kernel_client(client->seq_client); + synths[card->number] = NULL; + kfree(client); + } + mutex_unlock(®ister_mutex); + return 0; +} + +static struct snd_seq_driver seq_midisynth_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_midisynth_probe, + .remove = snd_seq_midisynth_remove, + }, + .id = SNDRV_SEQ_DEV_ID_MIDISYNTH, + .argsize = 0, +}; + +module_snd_seq_driver(seq_midisynth_driver); diff --git a/sound/core/seq/seq_midi_emul.c b/sound/core/seq/seq_midi_emul.c new file mode 100644 index 0000000000..81d2ef5e58 --- /dev/null +++ b/sound/core/seq/seq_midi_emul.c @@ -0,0 +1,723 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * GM/GS/XG midi module. + * + * Copyright (C) 1999 Steve Ratcliffe + * + * Based on awe_wave.c by Takashi Iwai + */ +/* + * This module is used to keep track of the current midi state. + * It can be used for drivers that are required to emulate midi when + * the hardware doesn't. + * + * It was written for a AWE64 driver, but there should be no AWE specific + * code in here. If there is it should be reported as a bug. + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_emul.h> +#include <sound/initval.h> +#include <sound/asoundef.h> + +MODULE_AUTHOR("Takashi Iwai / Steve Ratcliffe"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture sequencer MIDI emulation."); +MODULE_LICENSE("GPL"); + +/* Prototypes for static functions */ +static void note_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + int note, int vel); +static void do_control(const struct snd_midi_op *ops, void *private, + struct snd_midi_channel_set *chset, + struct snd_midi_channel *chan, + int control, int value); +static void rpn(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); +static void nrpn(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset); +static void sysex(const struct snd_midi_op *ops, void *private, + unsigned char *sysex, + int len, struct snd_midi_channel_set *chset); +static void all_sounds_off(const struct snd_midi_op *ops, void *private, + struct snd_midi_channel *chan); +static void all_notes_off(const struct snd_midi_op *ops, void *private, + struct snd_midi_channel *chan); +static void snd_midi_reset_controllers(struct snd_midi_channel *chan); +static void reset_all_channels(struct snd_midi_channel_set *chset); + + +/* + * Process an event in a driver independent way. This means dealing + * with RPN, NRPN, SysEx etc that are defined for common midi applications + * such as GM, GS and XG. + * There modes that this module will run in are: + * Generic MIDI - no interpretation at all, it will just save current values + * of controllers etc. + * GM - You can use all gm_ prefixed elements of chan. Controls, RPN, NRPN, + * SysEx will be interpreded as defined in General Midi. + * GS - You can use all gs_ prefixed elements of chan. Codes for GS will be + * interpreted. + * XG - You can use all xg_ prefixed elements of chan. Codes for XG will + * be interpreted. + */ +void +snd_midi_process_event(const struct snd_midi_op *ops, + struct snd_seq_event *ev, + struct snd_midi_channel_set *chanset) +{ + struct snd_midi_channel *chan; + void *drv; + int dest_channel = 0; + + if (ev == NULL || chanset == NULL) { + pr_debug("ALSA: seq_midi_emul: ev or chanbase NULL (snd_midi_process_event)\n"); + return; + } + if (chanset->channels == NULL) + return; + + if (snd_seq_ev_is_channel_type(ev)) { + dest_channel = ev->data.note.channel; + if (dest_channel >= chanset->max_channels) { + pr_debug("ALSA: seq_midi_emul: dest channel is %d, max is %d\n", + dest_channel, chanset->max_channels); + return; + } + } + + chan = chanset->channels + dest_channel; + drv = chanset->private_data; + + /* EVENT_NOTE should be processed before queued */ + if (ev->type == SNDRV_SEQ_EVENT_NOTE) + return; + + /* Make sure that we don't have a note on that should really be + * a note off */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON && ev->data.note.velocity == 0) + ev->type = SNDRV_SEQ_EVENT_NOTEOFF; + + /* Make sure the note is within array range */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON || + ev->type == SNDRV_SEQ_EVENT_NOTEOFF || + ev->type == SNDRV_SEQ_EVENT_KEYPRESS) { + if (ev->data.note.note >= 128) + return; + } + + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEON: + if (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON) { + if (ops->note_off) + ops->note_off(drv, ev->data.note.note, 0, chan); + } + chan->note[ev->data.note.note] = SNDRV_MIDI_NOTE_ON; + if (ops->note_on) + ops->note_on(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_NOTEOFF: + if (! (chan->note[ev->data.note.note] & SNDRV_MIDI_NOTE_ON)) + break; + if (ops->note_off) + note_off(ops, drv, chan, ev->data.note.note, ev->data.note.velocity); + break; + case SNDRV_SEQ_EVENT_KEYPRESS: + if (ops->key_press) + ops->key_press(drv, ev->data.note.note, ev->data.note.velocity, chan); + break; + case SNDRV_SEQ_EVENT_CONTROLLER: + do_control(ops, drv, chanset, chan, + ev->data.control.param, ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_PGMCHANGE: + chan->midi_program = ev->data.control.value; + break; + case SNDRV_SEQ_EVENT_PITCHBEND: + chan->midi_pitchbend = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_PITCHBEND, chan); + break; + case SNDRV_SEQ_EVENT_CHANPRESS: + chan->midi_pressure = ev->data.control.value; + if (ops->control) + ops->control(drv, MIDI_CTL_CHAN_PRESSURE, chan); + break; + case SNDRV_SEQ_EVENT_CONTROL14: + /* Best guess is that this is any of the 14 bit controller values */ + if (ev->data.control.param < 32) { + /* set low part first */ + chan->control[ev->data.control.param + 32] = + ev->data.control.value & 0x7f; + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ((ev->data.control.value>>7) & 0x7f)); + } else + do_control(ops, drv, chanset, chan, + ev->data.control.param, + ev->data.control.value); + break; + case SNDRV_SEQ_EVENT_NONREGPARAM: + /* Break it back into its controller values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_NONREG_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + nrpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_REGPARAM: + /* Break it back into its controller values */ + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + chan->control[MIDI_CTL_MSB_DATA_ENTRY] + = (ev->data.control.value >> 7) & 0x7f; + chan->control[MIDI_CTL_LSB_DATA_ENTRY] + = ev->data.control.value & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] + = (ev->data.control.param >> 7) & 0x7f; + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB] + = ev->data.control.param & 0x7f; + rpn(ops, drv, chan, chanset); + break; + case SNDRV_SEQ_EVENT_SYSEX: + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) == SNDRV_SEQ_EVENT_LENGTH_VARIABLE) { + unsigned char sysexbuf[64]; + int len; + len = snd_seq_expand_var_event(ev, sizeof(sysexbuf), sysexbuf, 1, 0); + if (len > 0) + sysex(ops, drv, sysexbuf, len, chanset); + } + break; + case SNDRV_SEQ_EVENT_SONGPOS: + case SNDRV_SEQ_EVENT_SONGSEL: + case SNDRV_SEQ_EVENT_CLOCK: + case SNDRV_SEQ_EVENT_START: + case SNDRV_SEQ_EVENT_CONTINUE: + case SNDRV_SEQ_EVENT_STOP: + case SNDRV_SEQ_EVENT_QFRAME: + case SNDRV_SEQ_EVENT_TEMPO: + case SNDRV_SEQ_EVENT_TIMESIGN: + case SNDRV_SEQ_EVENT_KEYSIGN: + goto not_yet; + case SNDRV_SEQ_EVENT_SENSING: + break; + case SNDRV_SEQ_EVENT_CLIENT_START: + case SNDRV_SEQ_EVENT_CLIENT_EXIT: + case SNDRV_SEQ_EVENT_CLIENT_CHANGE: + case SNDRV_SEQ_EVENT_PORT_START: + case SNDRV_SEQ_EVENT_PORT_EXIT: + case SNDRV_SEQ_EVENT_PORT_CHANGE: + case SNDRV_SEQ_EVENT_ECHO: + not_yet: + default: + /*pr_debug("ALSA: seq_midi_emul: Unimplemented event %d\n", ev->type);*/ + break; + } +} +EXPORT_SYMBOL(snd_midi_process_event); + + +/* + * release note + */ +static void +note_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan, + int note, int vel) +{ + if (chan->gm_hold) { + /* Hold this note until pedal is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else if (chan->note[note] & SNDRV_MIDI_NOTE_SOSTENUTO) { + /* Mark this note as release; it will be turned off when sostenuto + * is turned off */ + chan->note[note] |= SNDRV_MIDI_NOTE_RELEASED; + } else { + chan->note[note] = 0; + if (ops->note_off) + ops->note_off(drv, note, vel, chan); + } +} + +/* + * Do all driver independent operations for this controller and pass + * events that need to take place immediately to the driver. + */ +static void +do_control(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel_set *chset, + struct snd_midi_channel *chan, int control, int value) +{ + int i; + + if (control >= ARRAY_SIZE(chan->control)) + return; + + /* Switches */ + if ((control >=64 && control <=69) || (control >= 80 && control <= 83)) { + /* These are all switches; either off or on so set to 0 or 127 */ + value = (value >= 64)? 127: 0; + } + chan->control[control] = value; + + switch (control) { + case MIDI_CTL_SUSTAIN: + if (value == 0) { + /* Sustain has been released, turn off held notes */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + break; + case MIDI_CTL_PORTAMENTO: + break; + case MIDI_CTL_SOSTENUTO: + if (value) { + /* Mark each note that is currently held down */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_ON) + chan->note[i] |= SNDRV_MIDI_NOTE_SOSTENUTO; + } + } else { + /* release all notes that were held */ + for (i = 0; i < 128; i++) { + if (chan->note[i] & SNDRV_MIDI_NOTE_SOSTENUTO) { + chan->note[i] &= ~SNDRV_MIDI_NOTE_SOSTENUTO; + if (chan->note[i] & SNDRV_MIDI_NOTE_RELEASED) { + chan->note[i] = SNDRV_MIDI_NOTE_OFF; + if (ops->note_off) + ops->note_off(drv, i, 0, chan); + } + } + } + } + break; + case MIDI_CTL_MSB_DATA_ENTRY: + chan->control[MIDI_CTL_LSB_DATA_ENTRY] = 0; + fallthrough; + case MIDI_CTL_LSB_DATA_ENTRY: + if (chan->param_type == SNDRV_MIDI_PARAM_TYPE_REGISTERED) + rpn(ops, drv, chan, chset); + else + nrpn(ops, drv, chan, chset); + break; + case MIDI_CTL_REGIST_PARM_NUM_LSB: + case MIDI_CTL_REGIST_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_REGISTERED; + break; + case MIDI_CTL_NONREG_PARM_NUM_LSB: + case MIDI_CTL_NONREG_PARM_NUM_MSB: + chan->param_type = SNDRV_MIDI_PARAM_TYPE_NONREGISTERED; + break; + + case MIDI_CTL_ALL_SOUNDS_OFF: + all_sounds_off(ops, drv, chan); + break; + + case MIDI_CTL_ALL_NOTES_OFF: + all_notes_off(ops, drv, chan); + break; + + case MIDI_CTL_MSB_BANK: + if (chset->midi_mode == SNDRV_MIDI_MODE_XG) { + if (value == 127) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } + break; + case MIDI_CTL_LSB_BANK: + break; + + case MIDI_CTL_RESET_CONTROLLERS: + snd_midi_reset_controllers(chan); + break; + + case MIDI_CTL_SOFT_PEDAL: + case MIDI_CTL_LEGATO_FOOTSWITCH: + case MIDI_CTL_HOLD2: + case MIDI_CTL_SC1_SOUND_VARIATION: + case MIDI_CTL_SC2_TIMBRE: + case MIDI_CTL_SC3_RELEASE_TIME: + case MIDI_CTL_SC4_ATTACK_TIME: + case MIDI_CTL_SC5_BRIGHTNESS: + case MIDI_CTL_E1_REVERB_DEPTH: + case MIDI_CTL_E2_TREMOLO_DEPTH: + case MIDI_CTL_E3_CHORUS_DEPTH: + case MIDI_CTL_E4_DETUNE_DEPTH: + case MIDI_CTL_E5_PHASER_DEPTH: + goto notyet; + notyet: + default: + if (ops->control) + ops->control(drv, control, chan); + break; + } +} + + +/* + * initialize the MIDI status + */ +void +snd_midi_channel_set_clear(struct snd_midi_channel_set *chset) +{ + int i; + + chset->midi_mode = SNDRV_MIDI_MODE_GM; + chset->gs_master_volume = 127; + + for (i = 0; i < chset->max_channels; i++) { + struct snd_midi_channel *chan = chset->channels + i; + memset(chan->note, 0, sizeof(chan->note)); + + chan->midi_aftertouch = 0; + chan->midi_pressure = 0; + chan->midi_program = 0; + chan->midi_pitchbend = 0; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (i == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} +EXPORT_SYMBOL(snd_midi_channel_set_clear); + +/* + * Process a rpn message. + */ +static void +rpn(const struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + int type; + int val; + + if (chset->midi_mode != SNDRV_MIDI_MODE_NONE) { + type = (chan->control[MIDI_CTL_REGIST_PARM_NUM_MSB] << 8) | + chan->control[MIDI_CTL_REGIST_PARM_NUM_LSB]; + val = (chan->control[MIDI_CTL_MSB_DATA_ENTRY] << 7) | + chan->control[MIDI_CTL_LSB_DATA_ENTRY]; + + switch (type) { + case 0x0000: /* Pitch bend sensitivity */ + /* MSB only / 1 semitone per 128 */ + chan->gm_rpn_pitch_bend_range = val; + break; + + case 0x0001: /* fine tuning: */ + /* MSB/LSB, 8192=center, 100/8192 cent step */ + chan->gm_rpn_fine_tuning = val - 8192; + break; + + case 0x0002: /* coarse tuning */ + /* MSB only / 8192=center, 1 semitone per 128 */ + chan->gm_rpn_coarse_tuning = val - 8192; + break; + + case 0x7F7F: /* "lock-in" RPN */ + /* ignored */ + break; + } + } + /* should call nrpn or rpn callback here.. */ +} + +/* + * Process an nrpn message. + */ +static void +nrpn(const struct snd_midi_op *ops, void *drv, struct snd_midi_channel *chan, + struct snd_midi_channel_set *chset) +{ + /* parse XG NRPNs here if possible */ + if (ops->nrpn) + ops->nrpn(drv, chan, chset); +} + + +/* + * convert channel parameter in GS sysex + */ +static int +get_channel(unsigned char cmd) +{ + int p = cmd & 0x0f; + if (p == 0) + p = 9; + else if (p < 10) + p--; + return p; +} + + +/* + * Process a sysex message. + */ +static void +sysex(const struct snd_midi_op *ops, void *private, unsigned char *buf, int len, + struct snd_midi_channel_set *chset) +{ + /* GM on */ + static const unsigned char gm_on_macro[] = { + 0x7e,0x7f,0x09,0x01, + }; + /* XG on */ + static const unsigned char xg_on_macro[] = { + 0x43,0x10,0x4c,0x00,0x00,0x7e,0x00, + }; + /* GS prefix + * drum channel: XX=0x1?(channel), YY=0x15, ZZ=on/off + * reverb mode: XX=0x01, YY=0x30, ZZ=0-7 + * chorus mode: XX=0x01, YY=0x38, ZZ=0-7 + * master vol: XX=0x00, YY=0x04, ZZ=0-127 + */ + static const unsigned char gs_pfx_macro[] = { + 0x41,0x10,0x42,0x12,0x40,/*XX,YY,ZZ*/ + }; + + int parsed = SNDRV_MIDI_SYSEX_NOT_PARSED; + + if (len <= 0 || buf[0] != 0xf0) + return; + /* skip first byte */ + buf++; + len--; + + /* GM on */ + if (len >= (int)sizeof(gm_on_macro) && + memcmp(buf, gm_on_macro, sizeof(gm_on_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) { + chset->midi_mode = SNDRV_MIDI_MODE_GM; + reset_all_channels(chset); + parsed = SNDRV_MIDI_SYSEX_GM_ON; + } + } + + /* GS macros */ + else if (len >= 8 && + memcmp(buf, gs_pfx_macro, sizeof(gs_pfx_macro)) == 0) { + if (chset->midi_mode != SNDRV_MIDI_MODE_GS && + chset->midi_mode != SNDRV_MIDI_MODE_XG) + chset->midi_mode = SNDRV_MIDI_MODE_GS; + + if (buf[5] == 0x00 && buf[6] == 0x7f && buf[7] == 0x00) { + /* GS reset */ + parsed = SNDRV_MIDI_SYSEX_GS_RESET; + reset_all_channels(chset); + } + + else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x15) { + /* drum pattern */ + int p = get_channel(buf[5]); + if (p < chset->max_channels) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + if (buf[7]) + chset->channels[p].drum_channel = 1; + else + chset->channels[p].drum_channel = 0; + } + + } else if ((buf[5] & 0xf0) == 0x10 && buf[6] == 0x21) { + /* program */ + int p = get_channel(buf[5]); + if (p < chset->max_channels && + ! chset->channels[p].drum_channel) { + parsed = SNDRV_MIDI_SYSEX_GS_DRUM_CHANNEL; + chset->channels[p].midi_program = buf[7]; + } + + } else if (buf[5] == 0x01 && buf[6] == 0x30) { + /* reverb mode */ + parsed = SNDRV_MIDI_SYSEX_GS_REVERB_MODE; + chset->gs_reverb_mode = buf[7]; + + } else if (buf[5] == 0x01 && buf[6] == 0x38) { + /* chorus mode */ + parsed = SNDRV_MIDI_SYSEX_GS_CHORUS_MODE; + chset->gs_chorus_mode = buf[7]; + + } else if (buf[5] == 0x00 && buf[6] == 0x04) { + /* master volume */ + parsed = SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME; + chset->gs_master_volume = buf[7]; + + } + } + + /* XG on */ + else if (len >= (int)sizeof(xg_on_macro) && + memcmp(buf, xg_on_macro, sizeof(xg_on_macro)) == 0) { + int i; + chset->midi_mode = SNDRV_MIDI_MODE_XG; + parsed = SNDRV_MIDI_SYSEX_XG_ON; + /* reset CC#0 for drums */ + for (i = 0; i < chset->max_channels; i++) { + if (chset->channels[i].drum_channel) + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 127; + else + chset->channels[i].control[MIDI_CTL_MSB_BANK] = 0; + } + } + + if (ops->sysex) + ops->sysex(private, buf - 1, len + 1, parsed, chset); +} + +/* + * all sound off + */ +static void +all_sounds_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan) +{ + int n; + + if (! ops->note_terminate) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n]) { + ops->note_terminate(drv, n, chan); + chan->note[n] = 0; + } + } +} + +/* + * all notes off + */ +static void +all_notes_off(const struct snd_midi_op *ops, void *drv, + struct snd_midi_channel *chan) +{ + int n; + + if (! ops->note_off) + return; + for (n = 0; n < 128; n++) { + if (chan->note[n] == SNDRV_MIDI_NOTE_ON) + note_off(ops, drv, chan, n, 0); + } +} + +/* + * Initialise a single midi channel control block. + */ +static void snd_midi_channel_init(struct snd_midi_channel *p, int n) +{ + if (p == NULL) + return; + + memset(p, 0, sizeof(struct snd_midi_channel)); + p->private = NULL; + p->number = n; + + snd_midi_reset_controllers(p); + p->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + p->gm_rpn_fine_tuning = 0; + p->gm_rpn_coarse_tuning = 0; + + if (n == 9) + p->drum_channel = 1; /* Default ch 10 as drums */ +} + +/* + * Allocate and initialise a set of midi channel control blocks. + */ +static struct snd_midi_channel *snd_midi_channel_init_set(int n) +{ + struct snd_midi_channel *chan; + int i; + + chan = kmalloc_array(n, sizeof(struct snd_midi_channel), GFP_KERNEL); + if (chan) { + for (i = 0; i < n; i++) + snd_midi_channel_init(chan+i, i); + } + + return chan; +} + +/* + * reset all midi channels + */ +static void +reset_all_channels(struct snd_midi_channel_set *chset) +{ + int ch; + for (ch = 0; ch < chset->max_channels; ch++) { + struct snd_midi_channel *chan = chset->channels + ch; + snd_midi_reset_controllers(chan); + chan->gm_rpn_pitch_bend_range = 256; /* 2 semitones */ + chan->gm_rpn_fine_tuning = 0; + chan->gm_rpn_coarse_tuning = 0; + + if (ch == 9) + chan->drum_channel = 1; + else + chan->drum_channel = 0; + } +} + + +/* + * Allocate and initialise a midi channel set. + */ +struct snd_midi_channel_set *snd_midi_channel_alloc_set(int n) +{ + struct snd_midi_channel_set *chset; + + chset = kmalloc(sizeof(*chset), GFP_KERNEL); + if (chset) { + chset->channels = snd_midi_channel_init_set(n); + chset->private_data = NULL; + chset->max_channels = n; + } + return chset; +} +EXPORT_SYMBOL(snd_midi_channel_alloc_set); + +/* + * Reset the midi controllers on a particular channel to default values. + */ +static void snd_midi_reset_controllers(struct snd_midi_channel *chan) +{ + memset(chan->control, 0, sizeof(chan->control)); + chan->gm_volume = 127; + chan->gm_expression = 127; + chan->gm_pan = 64; +} + + +/* + * Free a midi channel set. + */ +void snd_midi_channel_free_set(struct snd_midi_channel_set *chset) +{ + if (chset == NULL) + return; + kfree(chset->channels); + kfree(chset); +} +EXPORT_SYMBOL(snd_midi_channel_free_set); diff --git a/sound/core/seq/seq_midi_event.c b/sound/core/seq/seq_midi_event.c new file mode 100644 index 0000000000..7511462fe0 --- /dev/null +++ b/sound/core/seq/seq_midi_event.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MIDI byte <-> sequencer event coder + * + * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>, + * Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_event.h> +#include <sound/asoundef.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>, Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("MIDI byte <-> sequencer event coder"); +MODULE_LICENSE("GPL"); + +/* event type, index into status_event[] */ +/* from 0 to 6 are normal commands (note off, on, etc.) for 0x9?-0xe? */ +#define ST_INVALID 7 +#define ST_SPECIAL 8 +#define ST_SYSEX ST_SPECIAL +/* from 8 to 15 are events for 0xf0-0xf7 */ + + +/* + * prototypes + */ +static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev); +static void note_decode(struct snd_seq_event *ev, unsigned char *buf); +static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf); +static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf); +static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf); +static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf); + +/* + * event list + */ +static struct status_event_list { + int event; + int qlen; + void (*encode)(struct snd_midi_event *dev, struct snd_seq_event *ev); + void (*decode)(struct snd_seq_event *ev, unsigned char *buf); +} status_event[] = { + /* 0x80 - 0xef */ + {SNDRV_SEQ_EVENT_NOTEOFF, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_NOTEON, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_KEYPRESS, 2, note_event, note_decode}, + {SNDRV_SEQ_EVENT_CONTROLLER, 2, two_param_ctrl_event, two_param_decode}, + {SNDRV_SEQ_EVENT_PGMCHANGE, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_CHANPRESS, 1, one_param_ctrl_event, one_param_decode}, + {SNDRV_SEQ_EVENT_PITCHBEND, 2, pitchbend_ctrl_event, pitchbend_decode}, + /* invalid */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, + /* 0xf0 - 0xff */ + {SNDRV_SEQ_EVENT_SYSEX, 1, NULL, NULL}, /* sysex: 0xf0 */ + {SNDRV_SEQ_EVENT_QFRAME, 1, one_param_event, one_param_decode}, /* 0xf1 */ + {SNDRV_SEQ_EVENT_SONGPOS, 2, songpos_event, songpos_decode}, /* 0xf2 */ + {SNDRV_SEQ_EVENT_SONGSEL, 1, one_param_event, one_param_decode}, /* 0xf3 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf4 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf5 */ + {SNDRV_SEQ_EVENT_TUNE_REQUEST, 0, NULL, NULL}, /* 0xf6 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf7 */ + {SNDRV_SEQ_EVENT_CLOCK, 0, NULL, NULL}, /* 0xf8 */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xf9 */ + {SNDRV_SEQ_EVENT_START, 0, NULL, NULL}, /* 0xfa */ + {SNDRV_SEQ_EVENT_CONTINUE, 0, NULL, NULL}, /* 0xfb */ + {SNDRV_SEQ_EVENT_STOP, 0, NULL, NULL}, /* 0xfc */ + {SNDRV_SEQ_EVENT_NONE, -1, NULL, NULL}, /* 0xfd */ + {SNDRV_SEQ_EVENT_SENSING, 0, NULL, NULL}, /* 0xfe */ + {SNDRV_SEQ_EVENT_RESET, 0, NULL, NULL}, /* 0xff */ +}; + +static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, int len, + struct snd_seq_event *ev); +static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, int count, + struct snd_seq_event *ev); + +static struct extra_event_list { + int event; + int (*decode)(struct snd_midi_event *dev, unsigned char *buf, int len, + struct snd_seq_event *ev); +} extra_event[] = { + {SNDRV_SEQ_EVENT_CONTROL14, extra_decode_ctrl14}, + {SNDRV_SEQ_EVENT_NONREGPARAM, extra_decode_xrpn}, + {SNDRV_SEQ_EVENT_REGPARAM, extra_decode_xrpn}, +}; + +/* + * new/delete record + */ + +int snd_midi_event_new(int bufsize, struct snd_midi_event **rdev) +{ + struct snd_midi_event *dev; + + *rdev = NULL; + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + if (bufsize > 0) { + dev->buf = kmalloc(bufsize, GFP_KERNEL); + if (dev->buf == NULL) { + kfree(dev); + return -ENOMEM; + } + } + dev->bufsize = bufsize; + dev->lastcmd = 0xff; + dev->type = ST_INVALID; + spin_lock_init(&dev->lock); + *rdev = dev; + return 0; +} +EXPORT_SYMBOL(snd_midi_event_new); + +void snd_midi_event_free(struct snd_midi_event *dev) +{ + if (dev != NULL) { + kfree(dev->buf); + kfree(dev); + } +} +EXPORT_SYMBOL(snd_midi_event_free); + +/* + * initialize record + */ +static inline void reset_encode(struct snd_midi_event *dev) +{ + dev->read = 0; + dev->qlen = 0; + dev->type = ST_INVALID; +} + +void snd_midi_event_reset_encode(struct snd_midi_event *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + reset_encode(dev); + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL(snd_midi_event_reset_encode); + +void snd_midi_event_reset_decode(struct snd_midi_event *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + dev->lastcmd = 0xff; + spin_unlock_irqrestore(&dev->lock, flags); +} +EXPORT_SYMBOL(snd_midi_event_reset_decode); + +void snd_midi_event_no_status(struct snd_midi_event *dev, int on) +{ + dev->nostat = on ? 1 : 0; +} +EXPORT_SYMBOL(snd_midi_event_no_status); + +/* + * read one byte and encode to sequencer event: + * return true if MIDI bytes are encoded to an event + * false data is not finished + */ +bool snd_midi_event_encode_byte(struct snd_midi_event *dev, unsigned char c, + struct snd_seq_event *ev) +{ + bool rc = false; + unsigned long flags; + + if (c >= MIDI_CMD_COMMON_CLOCK) { + /* real-time event */ + ev->type = status_event[ST_SPECIAL + c - 0xf0].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + return ev->type != SNDRV_SEQ_EVENT_NONE; + } + + spin_lock_irqsave(&dev->lock, flags); + if ((c & 0x80) && + (c != MIDI_CMD_COMMON_SYSEX_END || dev->type != ST_SYSEX)) { + /* new command */ + dev->buf[0] = c; + if ((c & 0xf0) == 0xf0) /* system messages */ + dev->type = (c & 0x0f) + ST_SPECIAL; + else + dev->type = (c >> 4) & 0x07; + dev->read = 1; + dev->qlen = status_event[dev->type].qlen; + } else { + if (dev->qlen > 0) { + /* rest of command */ + dev->buf[dev->read++] = c; + if (dev->type != ST_SYSEX) + dev->qlen--; + } else { + /* running status */ + dev->buf[1] = c; + dev->qlen = status_event[dev->type].qlen - 1; + dev->read = 2; + } + } + if (dev->qlen == 0) { + ev->type = status_event[dev->type].event; + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (status_event[dev->type].encode) /* set data values */ + status_event[dev->type].encode(dev, ev); + if (dev->type >= ST_SPECIAL) + dev->type = ST_INVALID; + rc = true; + } else if (dev->type == ST_SYSEX) { + if (c == MIDI_CMD_COMMON_SYSEX_END || + dev->read >= dev->bufsize) { + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->data.ext.len = dev->read; + ev->data.ext.ptr = dev->buf; + if (c != MIDI_CMD_COMMON_SYSEX_END) + dev->read = 0; /* continue to parse */ + else + reset_encode(dev); /* all parsed */ + rc = true; + } + } + + spin_unlock_irqrestore(&dev->lock, flags); + return rc; +} +EXPORT_SYMBOL(snd_midi_event_encode_byte); + +/* encode note event */ +static void note_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.note.channel = dev->buf[0] & 0x0f; + ev->data.note.note = dev->buf[1]; + ev->data.note.velocity = dev->buf[2]; +} + +/* encode one parameter controls */ +static void one_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = dev->buf[1]; +} + +/* encode pitch wheel change */ +static void pitchbend_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1] - 8192; +} + +/* encode midi control change */ +static void two_param_ctrl_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.channel = dev->buf[0] & 0x0f; + ev->data.control.param = dev->buf[1]; + ev->data.control.value = dev->buf[2]; +} + +/* encode one parameter value*/ +static void one_param_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.value = dev->buf[1]; +} + +/* encode song position */ +static void songpos_event(struct snd_midi_event *dev, struct snd_seq_event *ev) +{ + ev->data.control.value = (int)dev->buf[2] * 128 + (int)dev->buf[1]; +} + +/* + * decode from a sequencer event to midi bytes + * return the size of decoded midi events + */ +long snd_midi_event_decode(struct snd_midi_event *dev, unsigned char *buf, long count, + struct snd_seq_event *ev) +{ + unsigned int cmd, type; + + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return -ENOENT; + + for (type = 0; type < ARRAY_SIZE(status_event); type++) { + if (ev->type == status_event[type].event) + goto __found; + } + for (type = 0; type < ARRAY_SIZE(extra_event); type++) { + if (ev->type == extra_event[type].event) + return extra_event[type].decode(dev, buf, count, ev); + } + return -ENOENT; + + __found: + if (type >= ST_SPECIAL) + cmd = 0xf0 + (type - ST_SPECIAL); + else + /* data.note.channel and data.control.channel is identical */ + cmd = 0x80 | (type << 4) | (ev->data.note.channel & 0x0f); + + + if (cmd == MIDI_CMD_COMMON_SYSEX) { + snd_midi_event_reset_decode(dev); + return snd_seq_expand_var_event(ev, count, buf, 1, 0); + } else { + int qlen; + unsigned char xbuf[4]; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if ((cmd & 0xf0) == 0xf0 || dev->lastcmd != cmd || dev->nostat) { + dev->lastcmd = cmd; + spin_unlock_irqrestore(&dev->lock, flags); + xbuf[0] = cmd; + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 1); + qlen = status_event[type].qlen + 1; + } else { + spin_unlock_irqrestore(&dev->lock, flags); + if (status_event[type].decode) + status_event[type].decode(ev, xbuf + 0); + qlen = status_event[type].qlen; + } + if (count < qlen) + return -ENOMEM; + memcpy(buf, xbuf, qlen); + return qlen; + } +} +EXPORT_SYMBOL(snd_midi_event_decode); + + +/* decode note event */ +static void note_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.note.note & 0x7f; + buf[1] = ev->data.note.velocity & 0x7f; +} + +/* decode one parameter controls */ +static void one_param_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; +} + +/* decode pitch wheel change */ +static void pitchbend_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + int value = ev->data.control.value + 8192; + buf[0] = value & 0x7f; + buf[1] = (value >> 7) & 0x7f; +} + +/* decode midi control change */ +static void two_param_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.param & 0x7f; + buf[1] = ev->data.control.value & 0x7f; +} + +/* decode song position */ +static void songpos_decode(struct snd_seq_event *ev, unsigned char *buf) +{ + buf[0] = ev->data.control.value & 0x7f; + buf[1] = (ev->data.control.value >> 7) & 0x7f; +} + +/* decode 14bit control */ +static int extra_decode_ctrl14(struct snd_midi_event *dev, unsigned char *buf, + int count, struct snd_seq_event *ev) +{ + unsigned char cmd; + int idx = 0; + + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + if (ev->data.control.param < 0x20) { + if (count < 4) + return -ENOMEM; + if (dev->nostat && count < 6) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 5) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param; + buf[idx++] = (ev->data.control.value >> 7) & 0x7f; + if (dev->nostat) + buf[idx++] = cmd; + buf[idx++] = ev->data.control.param + 0x20; + buf[idx++] = ev->data.control.value & 0x7f; + } else { + if (count < 2) + return -ENOMEM; + if (cmd != dev->lastcmd || dev->nostat) { + if (count < 3) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + buf[idx++] = ev->data.control.param & 0x7f; + buf[idx++] = ev->data.control.value & 0x7f; + } + return idx; +} + +/* decode reg/nonreg param */ +static int extra_decode_xrpn(struct snd_midi_event *dev, unsigned char *buf, + int count, struct snd_seq_event *ev) +{ + unsigned char cmd; + const char *cbytes; + static const char cbytes_nrpn[4] = { MIDI_CTL_NONREG_PARM_NUM_MSB, + MIDI_CTL_NONREG_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + static const char cbytes_rpn[4] = { MIDI_CTL_REGIST_PARM_NUM_MSB, + MIDI_CTL_REGIST_PARM_NUM_LSB, + MIDI_CTL_MSB_DATA_ENTRY, + MIDI_CTL_LSB_DATA_ENTRY }; + unsigned char bytes[4]; + int idx = 0, i; + + if (count < 8) + return -ENOMEM; + if (dev->nostat && count < 12) + return -ENOMEM; + cmd = MIDI_CMD_CONTROL|(ev->data.control.channel & 0x0f); + bytes[0] = (ev->data.control.param & 0x3f80) >> 7; + bytes[1] = ev->data.control.param & 0x007f; + bytes[2] = (ev->data.control.value & 0x3f80) >> 7; + bytes[3] = ev->data.control.value & 0x007f; + if (cmd != dev->lastcmd && !dev->nostat) { + if (count < 9) + return -ENOMEM; + buf[idx++] = dev->lastcmd = cmd; + } + cbytes = ev->type == SNDRV_SEQ_EVENT_NONREGPARAM ? cbytes_nrpn : cbytes_rpn; + for (i = 0; i < 4; i++) { + if (dev->nostat) + buf[idx++] = dev->lastcmd = cmd; + buf[idx++] = cbytes[i]; + buf[idx++] = bytes[i]; + } + return idx; +} diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c new file mode 100644 index 0000000000..f3f14ff0f8 --- /dev/null +++ b/sound/core/seq/seq_ports.c @@ -0,0 +1,741 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + */ + +#include <sound/core.h> +#include <linux/slab.h> +#include <linux/module.h> +#include "seq_system.h" +#include "seq_ports.h" +#include "seq_clientmgr.h" + +/* + + registration of client ports + + */ + + +/* + +NOTE: the current implementation of the port structure as a linked list is +not optimal for clients that have many ports. For sending messages to all +subscribers of a port we first need to find the address of the port +structure, which means we have to traverse the list. A direct access table +(array) would be better, but big preallocated arrays waste memory. + +Possible actions: + +1) leave it this way, a client does normaly does not have more than a few +ports + +2) replace the linked list of ports by a array of pointers which is +dynamicly kmalloced. When a port is added or deleted we can simply allocate +a new array, copy the corresponding pointers, and delete the old one. We +then only need a pointer to this array, and an integer that tells us how +much elements are in array. + +*/ + +/* return pointer to port structure - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, + int num) +{ + struct snd_seq_client_port *port; + + if (client == NULL) + return NULL; + read_lock(&client->ports_lock); + list_for_each_entry(port, &client->ports_list_head, list) { + if (port->addr.port == num) { + if (port->closing) + break; /* deleting now */ + snd_use_lock_use(&port->use_lock); + read_unlock(&client->ports_lock); + return port; + } + } + read_unlock(&client->ports_lock); + return NULL; /* not found */ +} + + +/* search for the next port - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, + struct snd_seq_port_info *pinfo) +{ + int num; + struct snd_seq_client_port *port, *found; + bool check_inactive = (pinfo->capability & SNDRV_SEQ_PORT_CAP_INACTIVE); + + num = pinfo->addr.port; + found = NULL; + read_lock(&client->ports_lock); + list_for_each_entry(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ + if (port->addr.port < num) + continue; + if (port->addr.port == num) { + found = port; + break; + } + if (found == NULL || port->addr.port < found->addr.port) + found = port; + } + if (found) { + if (found->closing) + found = NULL; + else + snd_use_lock_use(&found->use_lock); + } + read_unlock(&client->ports_lock); + return found; +} + + +/* initialize snd_seq_port_subs_info */ +static void port_subs_info_init(struct snd_seq_port_subs_info *grp) +{ + INIT_LIST_HEAD(&grp->list_head); + grp->count = 0; + grp->exclusive = 0; + rwlock_init(&grp->list_lock); + init_rwsem(&grp->list_mutex); + grp->open = NULL; + grp->close = NULL; +} + + +/* create a port, port number or a negative error code is returned + * the caller needs to unref the port via snd_seq_port_unlock() appropriately + */ +int snd_seq_create_port(struct snd_seq_client *client, int port, + struct snd_seq_client_port **port_ret) +{ + struct snd_seq_client_port *new_port, *p; + int num; + + *port_ret = NULL; + + /* sanity check */ + if (snd_BUG_ON(!client)) + return -EINVAL; + + if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) { + pr_warn("ALSA: seq: too many ports for client %d\n", client->number); + return -EINVAL; + } + + /* create a new port */ + new_port = kzalloc(sizeof(*new_port), GFP_KERNEL); + if (!new_port) + return -ENOMEM; /* failure, out of memory */ + /* init port data */ + new_port->addr.client = client->number; + new_port->addr.port = -1; + new_port->owner = THIS_MODULE; + snd_use_lock_init(&new_port->use_lock); + port_subs_info_init(&new_port->c_src); + port_subs_info_init(&new_port->c_dest); + snd_use_lock_use(&new_port->use_lock); + + num = max(port, 0); + mutex_lock(&client->ports_mutex); + write_lock_irq(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + kfree(new_port); + num = -EBUSY; + goto unlock; + } + if (p->addr.port > num) + break; + if (port < 0) /* auto-probe mode */ + num = p->addr.port + 1; + } + /* insert the new port */ + list_add_tail(&new_port->list, &p->list); + client->num_ports++; + new_port->addr.port = num; /* store the port number in the port */ + sprintf(new_port->name, "port-%d", num); + *port_ret = new_port; + unlock: + write_unlock_irq(&client->ports_lock); + mutex_unlock(&client->ports_mutex); + + return num; +} + +/* */ +static int subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, int send_ack); +static int unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, int send_ack); + + +static struct snd_seq_client_port *get_client_port(struct snd_seq_addr *addr, + struct snd_seq_client **cp) +{ + struct snd_seq_client_port *p; + *cp = snd_seq_client_use_ptr(addr->client); + if (*cp) { + p = snd_seq_port_use_ptr(*cp, addr->port); + if (! p) { + snd_seq_client_unlock(*cp); + *cp = NULL; + } + return p; + } + return NULL; +} + +static void delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack); + +static inline struct snd_seq_subscribers * +get_subscriber(struct list_head *p, bool is_src) +{ + if (is_src) + return list_entry(p, struct snd_seq_subscribers, src_list); + else + return list_entry(p, struct snd_seq_subscribers, dest_list); +} + +/* + * remove all subscribers on the list + * this is called from port_delete, for each src and dest list. + */ +static void clear_subscriber_list(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + int is_src) +{ + struct list_head *p, *n; + + list_for_each_safe(p, n, &grp->list_head) { + struct snd_seq_subscribers *subs; + struct snd_seq_client *c; + struct snd_seq_client_port *aport; + + subs = get_subscriber(p, is_src); + if (is_src) + aport = get_client_port(&subs->info.dest, &c); + else + aport = get_client_port(&subs->info.sender, &c); + delete_and_unsubscribe_port(client, port, subs, is_src, false); + + if (!aport) { + /* looks like the connected port is being deleted. + * we decrease the counter, and when both ports are deleted + * remove the subscriber info + */ + if (atomic_dec_and_test(&subs->ref_count)) + kfree(subs); + continue; + } + + /* ok we got the connected port */ + delete_and_unsubscribe_port(c, aport, subs, !is_src, true); + kfree(subs); + snd_seq_port_unlock(aport); + snd_seq_client_unlock(c); + } +} + +/* delete port data */ +static int port_delete(struct snd_seq_client *client, + struct snd_seq_client_port *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + snd_use_lock_sync(&port->use_lock); + + /* clear subscribers info */ + clear_subscriber_list(client, port, &port->c_src, true); + clear_subscriber_list(client, port, &port->c_dest, false); + + if (port->private_free) + port->private_free(port->private_data); + + snd_BUG_ON(port->c_src.count != 0); + snd_BUG_ON(port->c_dest.count != 0); + + kfree(port); + return 0; +} + + +/* delete a port with the given port id */ +int snd_seq_delete_port(struct snd_seq_client *client, int port) +{ + struct snd_seq_client_port *found = NULL, *p; + + mutex_lock(&client->ports_mutex); + write_lock_irq(&client->ports_lock); + list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + /* ok found. delete from the list at first */ + list_del(&p->list); + client->num_ports--; + found = p; + break; + } + } + write_unlock_irq(&client->ports_lock); + mutex_unlock(&client->ports_mutex); + if (found) + return port_delete(client, found); + else + return -ENOENT; +} + +/* delete the all ports belonging to the given client */ +int snd_seq_delete_all_ports(struct snd_seq_client *client) +{ + struct list_head deleted_list; + struct snd_seq_client_port *port, *tmp; + + /* move the port list to deleted_list, and + * clear the port list in the client data. + */ + mutex_lock(&client->ports_mutex); + write_lock_irq(&client->ports_lock); + if (! list_empty(&client->ports_list_head)) { + list_add(&deleted_list, &client->ports_list_head); + list_del_init(&client->ports_list_head); + } else { + INIT_LIST_HEAD(&deleted_list); + } + client->num_ports = 0; + write_unlock_irq(&client->ports_lock); + + /* remove each port in deleted_list */ + list_for_each_entry_safe(port, tmp, &deleted_list, list) { + list_del(&port->list); + snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); + port_delete(client, port); + } + mutex_unlock(&client->ports_mutex); + return 0; +} + +/* set port info fields */ +int snd_seq_set_port_info(struct snd_seq_client_port * port, + struct snd_seq_port_info * info) +{ + if (snd_BUG_ON(!port || !info)) + return -EINVAL; + + /* set port name */ + if (info->name[0]) + strscpy(port->name, info->name, sizeof(port->name)); + + /* set capabilities */ + port->capability = info->capability; + + /* get port type */ + port->type = info->type; + + /* information about supported channels/voices */ + port->midi_channels = info->midi_channels; + port->midi_voices = info->midi_voices; + port->synth_voices = info->synth_voices; + + /* timestamping */ + port->timestamping = (info->flags & SNDRV_SEQ_PORT_FLG_TIMESTAMP) ? 1 : 0; + port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; + port->time_queue = info->time_queue; + + /* UMP direction and group */ + port->direction = info->direction; + port->ump_group = info->ump_group; + if (port->ump_group > SNDRV_UMP_MAX_GROUPS) + port->ump_group = 0; + + /* fill default port direction */ + if (!port->direction) { + if (info->capability & SNDRV_SEQ_PORT_CAP_READ) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (info->capability & SNDRV_SEQ_PORT_CAP_WRITE) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + } + + return 0; +} + +/* get port info fields */ +int snd_seq_get_port_info(struct snd_seq_client_port * port, + struct snd_seq_port_info * info) +{ + if (snd_BUG_ON(!port || !info)) + return -EINVAL; + + /* get port name */ + strscpy(info->name, port->name, sizeof(info->name)); + + /* get capabilities */ + info->capability = port->capability; + + /* get port type */ + info->type = port->type; + + /* information about supported channels/voices */ + info->midi_channels = port->midi_channels; + info->midi_voices = port->midi_voices; + info->synth_voices = port->synth_voices; + + /* get subscriber counts */ + info->read_use = port->c_src.count; + info->write_use = port->c_dest.count; + + /* timestamping */ + info->flags = 0; + if (port->timestamping) { + info->flags |= SNDRV_SEQ_PORT_FLG_TIMESTAMP; + if (port->time_real) + info->flags |= SNDRV_SEQ_PORT_FLG_TIME_REAL; + info->time_queue = port->time_queue; + } + + /* UMP direction and group */ + info->direction = port->direction; + info->ump_group = port->ump_group; + + return 0; +} + + + +/* + * call callback functions (if any): + * the callbacks are invoked only when the first (for connection) or + * the last subscription (for disconnection) is done. Second or later + * subscription results in increment of counter, but no callback is + * invoked. + * This feature is useful if these callbacks are associated with + * initialization or termination of devices (see seq_midi.c). + */ + +static int subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, + int send_ack) +{ + int err = 0; + + if (!try_module_get(port->owner)) + return -EFAULT; + grp->count++; + if (grp->open && grp->count == 1) { + err = grp->open(port->private_data, info); + if (err < 0) { + module_put(port->owner); + grp->count--; + } + } + if (err >= 0 && send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_SUBSCRIBED); + + return err; +} + +static int unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_port_subs_info *grp, + struct snd_seq_port_subscribe *info, + int send_ack) +{ + int err = 0; + + if (! grp->count) + return -EINVAL; + grp->count--; + if (grp->close && grp->count == 0) + err = grp->close(port->private_data, info); + if (send_ack && client->type == USER_CLIENT) + snd_seq_client_notify_subscription(port->addr.client, port->addr.port, + info, SNDRV_SEQ_EVENT_PORT_UNSUBSCRIBED); + module_put(port->owner); + return err; +} + + + +/* check if both addresses are identical */ +static inline int addr_match(struct snd_seq_addr *r, struct snd_seq_addr *s) +{ + return (r->client == s->client) && (r->port == s->port); +} + +/* check the two subscribe info match */ +/* if flags is zero, checks only sender and destination addresses */ +static int match_subs_info(struct snd_seq_port_subscribe *r, + struct snd_seq_port_subscribe *s) +{ + if (addr_match(&r->sender, &s->sender) && + addr_match(&r->dest, &s->dest)) { + if (r->flags && r->flags == s->flags) + return r->queue == s->queue; + else if (! r->flags) + return 1; + } + return 0; +} + +static int check_and_subscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool exclusive, bool ack) +{ + struct snd_seq_port_subs_info *grp; + struct list_head *p; + struct snd_seq_subscribers *s; + int err; + + grp = is_src ? &port->c_src : &port->c_dest; + err = -EBUSY; + down_write(&grp->list_mutex); + if (exclusive) { + if (!list_empty(&grp->list_head)) + goto __error; + } else { + if (grp->exclusive) + goto __error; + /* check whether already exists */ + list_for_each(p, &grp->list_head) { + s = get_subscriber(p, is_src); + if (match_subs_info(&subs->info, &s->info)) + goto __error; + } + } + + err = subscribe_port(client, port, grp, &subs->info, ack); + if (err < 0) { + grp->exclusive = 0; + goto __error; + } + + /* add to list */ + write_lock_irq(&grp->list_lock); + if (is_src) + list_add_tail(&subs->src_list, &grp->list_head); + else + list_add_tail(&subs->dest_list, &grp->list_head); + grp->exclusive = exclusive; + atomic_inc(&subs->ref_count); + write_unlock_irq(&grp->list_lock); + err = 0; + + __error: + up_write(&grp->list_mutex); + return err; +} + +/* called with grp->list_mutex held */ +static void __delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack) +{ + struct snd_seq_port_subs_info *grp; + struct list_head *list; + bool empty; + + grp = is_src ? &port->c_src : &port->c_dest; + list = is_src ? &subs->src_list : &subs->dest_list; + write_lock_irq(&grp->list_lock); + empty = list_empty(list); + if (!empty) + list_del_init(list); + grp->exclusive = 0; + write_unlock_irq(&grp->list_lock); + + if (!empty) + unsubscribe_port(client, port, grp, &subs->info, ack); +} + +static void delete_and_unsubscribe_port(struct snd_seq_client *client, + struct snd_seq_client_port *port, + struct snd_seq_subscribers *subs, + bool is_src, bool ack) +{ + struct snd_seq_port_subs_info *grp; + + grp = is_src ? &port->c_src : &port->c_dest; + down_write(&grp->list_mutex); + __delete_and_unsubscribe_port(client, port, subs, is_src, ack); + up_write(&grp->list_mutex); +} + +/* connect two ports */ +int snd_seq_port_connect(struct snd_seq_client *connector, + struct snd_seq_client *src_client, + struct snd_seq_client_port *src_port, + struct snd_seq_client *dest_client, + struct snd_seq_client_port *dest_port, + struct snd_seq_port_subscribe *info) +{ + struct snd_seq_subscribers *subs; + bool exclusive; + int err; + + subs = kzalloc(sizeof(*subs), GFP_KERNEL); + if (!subs) + return -ENOMEM; + + subs->info = *info; + atomic_set(&subs->ref_count, 0); + INIT_LIST_HEAD(&subs->src_list); + INIT_LIST_HEAD(&subs->dest_list); + + exclusive = !!(info->flags & SNDRV_SEQ_PORT_SUBS_EXCLUSIVE); + + err = check_and_subscribe_port(src_client, src_port, subs, true, + exclusive, + connector->number != src_client->number); + if (err < 0) + goto error; + err = check_and_subscribe_port(dest_client, dest_port, subs, false, + exclusive, + connector->number != dest_client->number); + if (err < 0) + goto error_dest; + + return 0; + + error_dest: + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + error: + kfree(subs); + return err; +} + +/* remove the connection */ +int snd_seq_port_disconnect(struct snd_seq_client *connector, + struct snd_seq_client *src_client, + struct snd_seq_client_port *src_port, + struct snd_seq_client *dest_client, + struct snd_seq_client_port *dest_port, + struct snd_seq_port_subscribe *info) +{ + struct snd_seq_port_subs_info *dest = &dest_port->c_dest; + struct snd_seq_subscribers *subs; + int err = -ENOENT; + + /* always start from deleting the dest port for avoiding concurrent + * deletions + */ + down_write(&dest->list_mutex); + /* look for the connection */ + list_for_each_entry(subs, &dest->list_head, dest_list) { + if (match_subs_info(info, &subs->info)) { + __delete_and_unsubscribe_port(dest_client, dest_port, + subs, false, + connector->number != dest_client->number); + err = 0; + break; + } + } + up_write(&dest->list_mutex); + if (err < 0) + return err; + + delete_and_unsubscribe_port(src_client, src_port, subs, true, + connector->number != src_client->number); + kfree(subs); + return 0; +} + + +/* get matched subscriber */ +int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + struct snd_seq_addr *dest_addr, + struct snd_seq_port_subscribe *subs) +{ + struct snd_seq_subscribers *s; + int err = -ENOENT; + + down_read(&src_grp->list_mutex); + list_for_each_entry(s, &src_grp->list_head, src_list) { + if (addr_match(dest_addr, &s->info.dest)) { + *subs = s->info; + err = 0; + break; + } + } + up_read(&src_grp->list_mutex); + return err; +} + +/* + * Attach a device driver that wants to receive events from the + * sequencer. Returns the new port number on success. + * A driver that wants to receive the events converted to midi, will + * use snd_seq_midisynth_register_port(). + */ +/* exported */ +int snd_seq_event_port_attach(int client, + struct snd_seq_port_callback *pcbp, + int cap, int type, int midi_channels, + int midi_voices, char *portname) +{ + struct snd_seq_port_info portinfo; + int ret; + + /* Set up the port */ + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + strscpy(portinfo.name, portname ? portname : "Unnamed port", + sizeof(portinfo.name)); + + portinfo.capability = cap; + portinfo.type = type; + portinfo.kernel = pcbp; + portinfo.midi_channels = midi_channels; + portinfo.midi_voices = midi_voices; + + /* Create it */ + ret = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + &portinfo); + + if (ret >= 0) + ret = portinfo.addr.port; + + return ret; +} +EXPORT_SYMBOL(snd_seq_event_port_attach); + +/* + * Detach the driver from a port. + */ +/* exported */ +int snd_seq_event_port_detach(int client, int port) +{ + struct snd_seq_port_info portinfo; + int err; + + memset(&portinfo, 0, sizeof(portinfo)); + portinfo.addr.client = client; + portinfo.addr.port = port; + err = snd_seq_kernel_client_ctl(client, + SNDRV_SEQ_IOCTL_DELETE_PORT, + &portinfo); + + return err; +} +EXPORT_SYMBOL(snd_seq_event_port_detach); diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h new file mode 100644 index 0000000000..b111382f69 --- /dev/null +++ b/sound/core/seq/seq_ports.h @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Ports + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_PORTS_H +#define __SND_SEQ_PORTS_H + +#include <sound/seq_kernel.h> +#include "seq_lock.h" + +/* list of 'exported' ports */ + +/* Client ports that are not exported are still accessible, but are + anonymous ports. + + If a port supports SUBSCRIPTION, that port can send events to all + subscribersto a special address, with address + (queue==SNDRV_SEQ_ADDRESS_SUBSCRIBERS). The message is then send to all + recipients that are registered in the subscription list. A typical + application for these SUBSCRIPTION events is handling of incoming MIDI + data. The port doesn't 'know' what other clients are interested in this + message. If for instance a MIDI recording application would like to receive + the events from that port, it will first have to subscribe with that port. + +*/ + +struct snd_seq_subscribers { + struct snd_seq_port_subscribe info; /* additional info */ + struct list_head src_list; /* link of sources */ + struct list_head dest_list; /* link of destinations */ + atomic_t ref_count; +}; + +struct snd_seq_port_subs_info { + struct list_head list_head; /* list of subscribed ports */ + unsigned int count; /* count of subscribers */ + unsigned int exclusive: 1; /* exclusive mode */ + struct rw_semaphore list_mutex; + rwlock_t list_lock; + int (*open)(void *private_data, struct snd_seq_port_subscribe *info); + int (*close)(void *private_data, struct snd_seq_port_subscribe *info); +}; + +/* context for converting from legacy control event to UMP packet */ +struct snd_seq_ump_midi2_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + +struct snd_seq_client_port { + + struct snd_seq_addr addr; /* client/port number */ + struct module *owner; /* owner of this port */ + char name[64]; /* port name */ + struct list_head list; /* port list */ + snd_use_lock_t use_lock; + + /* subscribers */ + struct snd_seq_port_subs_info c_src; /* read (sender) list */ + struct snd_seq_port_subs_info c_dest; /* write (dest) list */ + + int (*event_input)(struct snd_seq_event *ev, int direct, void *private_data, + int atomic, int hop); + void (*private_free)(void *private_data); + void *private_data; + unsigned int closing : 1; + unsigned int timestamping: 1; + unsigned int time_real: 1; + int time_queue; + + /* capability, inport, output, sync */ + unsigned int capability; /* port capability bits */ + unsigned int type; /* port type bits */ + + /* supported channels */ + int midi_channels; + int midi_voices; + int synth_voices; + + /* UMP direction and group */ + unsigned char direction; + unsigned char ump_group; + +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + struct snd_seq_ump_midi2_bank midi2_bank[16]; /* per channel */ +#endif +}; + +struct snd_seq_client; + +/* return pointer to port structure and lock port */ +struct snd_seq_client_port *snd_seq_port_use_ptr(struct snd_seq_client *client, int num); + +/* search for next port - port is locked if found */ +struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *client, + struct snd_seq_port_info *pinfo); + +/* unlock the port */ +#define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock) + +/* create a port, port number or a negative error code is returned */ +int snd_seq_create_port(struct snd_seq_client *client, int port_index, + struct snd_seq_client_port **port_ret); + +/* delete a port */ +int snd_seq_delete_port(struct snd_seq_client *client, int port); + +/* delete all ports */ +int snd_seq_delete_all_ports(struct snd_seq_client *client); + +/* set port info fields */ +int snd_seq_set_port_info(struct snd_seq_client_port *port, + struct snd_seq_port_info *info); + +/* get port info fields */ +int snd_seq_get_port_info(struct snd_seq_client_port *port, + struct snd_seq_port_info *info); + +/* add subscriber to subscription list */ +int snd_seq_port_connect(struct snd_seq_client *caller, + struct snd_seq_client *s, struct snd_seq_client_port *sp, + struct snd_seq_client *d, struct snd_seq_client_port *dp, + struct snd_seq_port_subscribe *info); + +/* remove subscriber from subscription list */ +int snd_seq_port_disconnect(struct snd_seq_client *caller, + struct snd_seq_client *s, struct snd_seq_client_port *sp, + struct snd_seq_client *d, struct snd_seq_client_port *dp, + struct snd_seq_port_subscribe *info); + +/* subscribe port */ +int snd_seq_port_subscribe(struct snd_seq_client_port *port, + struct snd_seq_port_subscribe *info); + +/* get matched subscriber */ +int snd_seq_port_get_subscription(struct snd_seq_port_subs_info *src_grp, + struct snd_seq_addr *dest_addr, + struct snd_seq_port_subscribe *subs); + +#endif diff --git a/sound/core/seq/seq_prioq.c b/sound/core/seq/seq_prioq.c new file mode 100644 index 0000000000..1d857981e8 --- /dev/null +++ b/sound/core/seq/seq_prioq.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/time.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "seq_timer.h" +#include "seq_prioq.h" + + +/* Implementation is a simple linked list for now... + + This priority queue orders the events on timestamp. For events with an + equeal timestamp the queue behaves as a FIFO. + + * + * +-------+ + * Head --> | first | + * +-------+ + * |next + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * | | + * +-------+ + * | + * +-----v-+ + * Tail --> | last | + * +-------+ + * + + */ + + + +/* create new prioq (constructor) */ +struct snd_seq_prioq *snd_seq_prioq_new(void) +{ + struct snd_seq_prioq *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + spin_lock_init(&f->lock); + f->head = NULL; + f->tail = NULL; + f->cells = 0; + + return f; +} + +/* delete prioq (destructor) */ +void snd_seq_prioq_delete(struct snd_seq_prioq **fifo) +{ + struct snd_seq_prioq *f = *fifo; + *fifo = NULL; + + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_delete() called with NULL prioq\n"); + return; + } + + /* release resources...*/ + /*....................*/ + + if (f->cells > 0) { + /* drain prioQ */ + while (f->cells > 0) + snd_seq_cell_free(snd_seq_prioq_cell_out(f, NULL)); + } + + kfree(f); +} + + + + +/* compare timestamp between events */ +/* return 1 if a >= b; 0 */ +static inline int compare_timestamp(struct snd_seq_event *a, + struct snd_seq_event *b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + return (snd_seq_compare_tick_time(&a->time.tick, &b->time.tick)); + } else { + /* compare real time */ + return (snd_seq_compare_real_time(&a->time.time, &b->time.time)); + } +} + +/* compare timestamp between events */ +/* return negative if a < b; + * zero if a = b; + * positive if a > b; + */ +static inline int compare_timestamp_rel(struct snd_seq_event *a, + struct snd_seq_event *b) +{ + if ((a->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) { + /* compare ticks */ + if (a->time.tick > b->time.tick) + return 1; + else if (a->time.tick == b->time.tick) + return 0; + else + return -1; + } else { + /* compare real time */ + if (a->time.time.tv_sec > b->time.time.tv_sec) + return 1; + else if (a->time.time.tv_sec == b->time.time.tv_sec) { + if (a->time.time.tv_nsec > b->time.time.tv_nsec) + return 1; + else if (a->time.time.tv_nsec == b->time.time.tv_nsec) + return 0; + else + return -1; + } else + return -1; + } +} + +/* enqueue cell to prioq */ +int snd_seq_prioq_cell_in(struct snd_seq_prioq * f, + struct snd_seq_event_cell * cell) +{ + struct snd_seq_event_cell *cur, *prev; + unsigned long flags; + int count; + int prior; + + if (snd_BUG_ON(!f || !cell)) + return -EINVAL; + + /* check flags */ + prior = (cell->event.flags & SNDRV_SEQ_PRIORITY_MASK); + + spin_lock_irqsave(&f->lock, flags); + + /* check if this element needs to inserted at the end (ie. ordered + data is inserted) This will be very likeley if a sequencer + application or midi file player is feeding us (sequential) data */ + if (f->tail && !prior) { + if (compare_timestamp(&cell->event, &f->tail->event)) { + /* add new cell to tail of the fifo */ + f->tail->next = cell; + f->tail = cell; + cell->next = NULL; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; + } + } + /* traverse list of elements to find the place where the new cell is + to be inserted... Note that this is a order n process ! */ + + prev = NULL; /* previous cell */ + cur = f->head; /* cursor */ + + count = 10000; /* FIXME: enough big, isn't it? */ + while (cur != NULL) { + /* compare timestamps */ + int rel = compare_timestamp_rel(&cell->event, &cur->event); + if (rel < 0) + /* new cell has earlier schedule time, */ + break; + else if (rel == 0 && prior) + /* equal schedule time and prior to others */ + break; + /* new cell has equal or larger schedule time, */ + /* move cursor to next cell */ + prev = cur; + cur = cur->next; + if (! --count) { + spin_unlock_irqrestore(&f->lock, flags); + pr_err("ALSA: seq: cannot find a pointer.. infinite loop?\n"); + return -EINVAL; + } + } + + /* insert it before cursor */ + if (prev != NULL) + prev->next = cell; + cell->next = cur; + + if (f->head == cur) /* this is the first cell, set head to it */ + f->head = cell; + if (cur == NULL) /* reached end of the list */ + f->tail = cell; + f->cells++; + spin_unlock_irqrestore(&f->lock, flags); + return 0; +} + +/* return 1 if the current time >= event timestamp */ +static int event_is_ready(struct snd_seq_event *ev, void *current_time) +{ + if ((ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) == SNDRV_SEQ_TIME_STAMP_TICK) + return snd_seq_compare_tick_time(current_time, &ev->time.tick); + else + return snd_seq_compare_real_time(current_time, &ev->time.time); +} + +/* dequeue cell from prioq */ +struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f, + void *current_time) +{ + struct snd_seq_event_cell *cell; + unsigned long flags; + + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return NULL; + } + spin_lock_irqsave(&f->lock, flags); + + cell = f->head; + if (cell && current_time && !event_is_ready(&cell->event, current_time)) + cell = NULL; + if (cell) { + f->head = cell->next; + + /* reset tail if this was the last element */ + if (f->tail == cell) + f->tail = NULL; + + cell->next = NULL; + f->cells--; + } + + spin_unlock_irqrestore(&f->lock, flags); + return cell; +} + +/* return number of events available in prioq */ +int snd_seq_prioq_avail(struct snd_seq_prioq * f) +{ + if (f == NULL) { + pr_debug("ALSA: seq: snd_seq_prioq_cell_in() called with NULL prioq\n"); + return 0; + } + return f->cells; +} + +static inline int prioq_match(struct snd_seq_event_cell *cell, + int client, int timestamp) +{ + if (cell->event.source.client == client || + cell->event.dest.client == client) + return 1; + if (!timestamp) + return 0; + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + if (cell->event.time.tick) + return 1; + break; + case SNDRV_SEQ_TIME_STAMP_REAL: + if (cell->event.time.time.tv_sec || + cell->event.time.time.tv_nsec) + return 1; + break; + } + return 0; +} + +/* remove cells for left client */ +void snd_seq_prioq_leave(struct snd_seq_prioq * f, int client, int timestamp) +{ + register struct snd_seq_event_cell *cell, *next; + unsigned long flags; + struct snd_seq_event_cell *prev = NULL; + struct snd_seq_event_cell *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + while (cell) { + next = cell->next; + if (prioq_match(cell, client, timestamp)) { + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + freeprev = cell; + } else { +#if 0 + pr_debug("ALSA: seq: type = %i, source = %i, dest = %i, " + "client = %i\n", + cell->event.type, + cell->event.source.client, + cell->event.dest.client, + client); +#endif + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + +static int prioq_remove_match(struct snd_seq_remove_events *info, + struct snd_seq_event *ev) +{ + int res; + + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) { + if (ev->dest.client != info->dest.client || + ev->dest.port != info->dest.port) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_DEST_CHANNEL) { + if (! snd_seq_ev_is_channel_type(ev)) + return 0; + /* data.note.channel and data.control.channel are identical */ + if (ev->data.note.channel != info->channel) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_AFTER) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (!res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_BEFORE) { + if (info->remove_mode & SNDRV_SEQ_REMOVE_TIME_TICK) + res = snd_seq_compare_tick_time(&ev->time.tick, &info->time.tick); + else + res = snd_seq_compare_real_time(&ev->time.time, &info->time.time); + if (res) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_EVENT_TYPE) { + if (ev->type != info->type) + return 0; + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_IGNORE_OFF) { + /* Do not remove off events */ + switch (ev->type) { + case SNDRV_SEQ_EVENT_NOTEOFF: + /* case SNDRV_SEQ_EVENT_SAMPLE_STOP: */ + return 0; + default: + break; + } + } + if (info->remove_mode & SNDRV_SEQ_REMOVE_TAG_MATCH) { + if (info->tag != ev->tag) + return 0; + } + + return 1; +} + +/* remove cells matching remove criteria */ +void snd_seq_prioq_remove_events(struct snd_seq_prioq * f, int client, + struct snd_seq_remove_events *info) +{ + struct snd_seq_event_cell *cell, *next; + unsigned long flags; + struct snd_seq_event_cell *prev = NULL; + struct snd_seq_event_cell *freefirst = NULL, *freeprev = NULL, *freenext; + + /* collect all removed cells */ + spin_lock_irqsave(&f->lock, flags); + cell = f->head; + + while (cell) { + next = cell->next; + if (cell->event.source.client == client && + prioq_remove_match(info, &cell->event)) { + + /* remove cell from prioq */ + if (cell == f->head) { + f->head = cell->next; + } else { + prev->next = cell->next; + } + + if (cell == f->tail) + f->tail = cell->next; + f->cells--; + + /* add cell to free list */ + cell->next = NULL; + if (freefirst == NULL) { + freefirst = cell; + } else { + freeprev->next = cell; + } + + freeprev = cell; + } else { + prev = cell; + } + cell = next; + } + spin_unlock_irqrestore(&f->lock, flags); + + /* remove selected cells */ + while (freefirst) { + freenext = freefirst->next; + snd_seq_cell_free(freefirst); + freefirst = freenext; + } +} + + diff --git a/sound/core/seq/seq_prioq.h b/sound/core/seq/seq_prioq.h new file mode 100644 index 0000000000..5811a87deb --- /dev/null +++ b/sound/core/seq/seq_prioq.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Priority Queue + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_PRIOQ_H +#define __SND_SEQ_PRIOQ_H + +#include "seq_memory.h" + + +/* === PRIOQ === */ + +struct snd_seq_prioq { + struct snd_seq_event_cell *head; /* pointer to head of prioq */ + struct snd_seq_event_cell *tail; /* pointer to tail of prioq */ + int cells; + spinlock_t lock; +}; + + +/* create new prioq (constructor) */ +struct snd_seq_prioq *snd_seq_prioq_new(void); + +/* delete prioq (destructor) */ +void snd_seq_prioq_delete(struct snd_seq_prioq **fifo); + +/* enqueue cell to prioq */ +int snd_seq_prioq_cell_in(struct snd_seq_prioq *f, struct snd_seq_event_cell *cell); + +/* dequeue cell from prioq */ +struct snd_seq_event_cell *snd_seq_prioq_cell_out(struct snd_seq_prioq *f, + void *current_time); + +/* return number of events available in prioq */ +int snd_seq_prioq_avail(struct snd_seq_prioq *f); + +/* client left queue */ +void snd_seq_prioq_leave(struct snd_seq_prioq *f, int client, int timestamp); + +/* Remove events */ +void snd_seq_prioq_remove_events(struct snd_seq_prioq *f, int client, + struct snd_seq_remove_events *info); + +#endif diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c new file mode 100644 index 0000000000..bc933104c3 --- /dev/null +++ b/sound/core/seq/seq_queue.c @@ -0,0 +1,774 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Timing queue handling + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * + * MAJOR CHANGES + * Nov. 13, 1999 Takashi Iwai <iwai@ww.uni-erlangen.de> + * - Queues are allocated dynamically via ioctl. + * - When owner client is deleted, all owned queues are deleted, too. + * - Owner of unlocked queue is kept unmodified even if it is + * manipulated by other clients. + * - Owner field in SET_QUEUE_OWNER ioctl must be identical with the + * caller client. i.e. Changing owner to a third client is not + * allowed. + * + * Aug. 30, 2000 Takashi Iwai + * - Queues are managed in static array again, but with better way. + * The API itself is identical. + * - The queue is locked when struct snd_seq_queue pointer is returned via + * queueptr(). This pointer *MUST* be released afterward by + * queuefree(ptr). + * - Addition of experimental sync support. + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> + +#include "seq_memory.h" +#include "seq_queue.h" +#include "seq_clientmgr.h" +#include "seq_fifo.h" +#include "seq_timer.h" +#include "seq_info.h" + +/* list of allocated queues */ +static struct snd_seq_queue *queue_list[SNDRV_SEQ_MAX_QUEUES]; +static DEFINE_SPINLOCK(queue_list_lock); +/* number of queues allocated */ +static int num_queues; + +int snd_seq_queue_get_cur_queues(void) +{ + return num_queues; +} + +/*----------------------------------------------------------------*/ + +/* assign queue id and insert to list */ +static int queue_list_add(struct snd_seq_queue *q) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (! queue_list[i]) { + queue_list[i] = q; + q->queue = i; + num_queues++; + spin_unlock_irqrestore(&queue_list_lock, flags); + return i; + } + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return -1; +} + +static struct snd_seq_queue *queue_list_remove(int id, int client) +{ + struct snd_seq_queue *q; + unsigned long flags; + + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[id]; + if (q) { + spin_lock(&q->owner_lock); + if (q->owner == client) { + /* found */ + q->klocked = 1; + spin_unlock(&q->owner_lock); + queue_list[id] = NULL; + num_queues--; + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; + } + spin_unlock(&q->owner_lock); + } + spin_unlock_irqrestore(&queue_list_lock, flags); + return NULL; +} + +/*----------------------------------------------------------------*/ + +/* create new queue (constructor) */ +static struct snd_seq_queue *queue_new(int owner, int locked) +{ + struct snd_seq_queue *q; + + q = kzalloc(sizeof(*q), GFP_KERNEL); + if (!q) + return NULL; + + spin_lock_init(&q->owner_lock); + spin_lock_init(&q->check_lock); + mutex_init(&q->timer_mutex); + snd_use_lock_init(&q->use_lock); + q->queue = -1; + + q->tickq = snd_seq_prioq_new(); + q->timeq = snd_seq_prioq_new(); + q->timer = snd_seq_timer_new(); + if (q->tickq == NULL || q->timeq == NULL || q->timer == NULL) { + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + kfree(q); + return NULL; + } + + q->owner = owner; + q->locked = locked; + q->klocked = 0; + + return q; +} + +/* delete queue (destructor) */ +static void queue_delete(struct snd_seq_queue *q) +{ + /* stop and release the timer */ + mutex_lock(&q->timer_mutex); + snd_seq_timer_stop(q->timer); + snd_seq_timer_close(q); + mutex_unlock(&q->timer_mutex); + /* wait until access free */ + snd_use_lock_sync(&q->use_lock); + /* release resources... */ + snd_seq_prioq_delete(&q->tickq); + snd_seq_prioq_delete(&q->timeq); + snd_seq_timer_delete(&q->timer); + + kfree(q); +} + + +/*----------------------------------------------------------------*/ + +/* delete all existing queues */ +void snd_seq_queues_delete(void) +{ + int i; + + /* clear list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + if (queue_list[i]) + queue_delete(queue_list[i]); + } +} + +static void queue_use(struct snd_seq_queue *queue, int client, int use); + +/* allocate a new queue - + * return pointer to new queue or ERR_PTR(-errno) for error + * The new queue's use_lock is set to 1. It is the caller's responsibility to + * call snd_use_lock_free(&q->use_lock). + */ +struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int info_flags) +{ + struct snd_seq_queue *q; + + q = queue_new(client, locked); + if (q == NULL) + return ERR_PTR(-ENOMEM); + q->info_flags = info_flags; + queue_use(q, client, 1); + snd_use_lock_use(&q->use_lock); + if (queue_list_add(q) < 0) { + snd_use_lock_free(&q->use_lock); + queue_delete(q); + return ERR_PTR(-ENOMEM); + } + return q; +} + +/* delete a queue - queue must be owned by the client */ +int snd_seq_queue_delete(int client, int queueid) +{ + struct snd_seq_queue *q; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return -EINVAL; + q = queue_list_remove(queueid, client); + if (q == NULL) + return -EINVAL; + queue_delete(q); + + return 0; +} + + +/* return pointer to queue structure for specified id */ +struct snd_seq_queue *queueptr(int queueid) +{ + struct snd_seq_queue *q; + unsigned long flags; + + if (queueid < 0 || queueid >= SNDRV_SEQ_MAX_QUEUES) + return NULL; + spin_lock_irqsave(&queue_list_lock, flags); + q = queue_list[queueid]; + if (q) + snd_use_lock_use(&q->use_lock); + spin_unlock_irqrestore(&queue_list_lock, flags); + return q; +} + +/* return the (first) queue matching with the specified name */ +struct snd_seq_queue *snd_seq_queue_find_name(char *name) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (q) { + if (strncmp(q->name, name, sizeof(q->name)) == 0) + return q; + queuefree(q); + } + } + return NULL; +} + + +/* -------------------------------------------------------- */ + +#define MAX_CELL_PROCESSES_IN_QUEUE 1000 + +void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop) +{ + unsigned long flags; + struct snd_seq_event_cell *cell; + snd_seq_tick_time_t cur_tick; + snd_seq_real_time_t cur_time; + int processed = 0; + + if (q == NULL) + return; + + /* make this function non-reentrant */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_blocked) { + q->check_again = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + return; /* other thread is already checking queues */ + } + q->check_blocked = 1; + spin_unlock_irqrestore(&q->check_lock, flags); + + __again: + /* Process tick queue... */ + cur_tick = snd_seq_timer_get_cur_tick(q->timer); + for (;;) { + cell = snd_seq_prioq_cell_out(q->tickq, &cur_tick); + if (!cell) + break; + snd_seq_dispatch_event(cell, atomic, hop); + if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE) + goto out; /* the rest processed at the next batch */ + } + + /* Process time queue... */ + cur_time = snd_seq_timer_get_cur_time(q->timer, false); + for (;;) { + cell = snd_seq_prioq_cell_out(q->timeq, &cur_time); + if (!cell) + break; + snd_seq_dispatch_event(cell, atomic, hop); + if (++processed >= MAX_CELL_PROCESSES_IN_QUEUE) + goto out; /* the rest processed at the next batch */ + } + + out: + /* free lock */ + spin_lock_irqsave(&q->check_lock, flags); + if (q->check_again) { + q->check_again = 0; + if (processed < MAX_CELL_PROCESSES_IN_QUEUE) { + spin_unlock_irqrestore(&q->check_lock, flags); + goto __again; + } + } + q->check_blocked = 0; + spin_unlock_irqrestore(&q->check_lock, flags); +} + + +/* enqueue a event to singe queue */ +int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop) +{ + int dest, err; + struct snd_seq_queue *q; + + if (snd_BUG_ON(!cell)) + return -EINVAL; + dest = cell->event.queue; /* destination queue */ + q = queueptr(dest); + if (q == NULL) + return -EINVAL; + /* handle relative time stamps, convert them into absolute */ + if ((cell->event.flags & SNDRV_SEQ_TIME_MODE_MASK) == SNDRV_SEQ_TIME_MODE_REL) { + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + cell->event.time.tick += q->timer->tick.cur_tick; + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + snd_seq_inc_real_time(&cell->event.time.time, + &q->timer->cur_time); + break; + } + cell->event.flags &= ~SNDRV_SEQ_TIME_MODE_MASK; + cell->event.flags |= SNDRV_SEQ_TIME_MODE_ABS; + } + /* enqueue event in the real-time or midi queue */ + switch (cell->event.flags & SNDRV_SEQ_TIME_STAMP_MASK) { + case SNDRV_SEQ_TIME_STAMP_TICK: + err = snd_seq_prioq_cell_in(q->tickq, cell); + break; + + case SNDRV_SEQ_TIME_STAMP_REAL: + default: + err = snd_seq_prioq_cell_in(q->timeq, cell); + break; + } + + if (err < 0) { + queuefree(q); /* unlock */ + return err; + } + + /* trigger dispatching */ + snd_seq_check_queue(q, atomic, hop); + + queuefree(q); /* unlock */ + + return 0; +} + + +/*----------------------------------------------------------------*/ + +static inline int check_access(struct snd_seq_queue *q, int client) +{ + return (q->owner == client) || (!q->locked && !q->klocked); +} + +/* check if the client has permission to modify queue parameters. + * if it does, lock the queue + */ +static int queue_access_lock(struct snd_seq_queue *q, int client) +{ + unsigned long flags; + int access_ok; + + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + if (access_ok) + q->klocked = 1; + spin_unlock_irqrestore(&q->owner_lock, flags); + return access_ok; +} + +/* unlock the queue */ +static inline void queue_access_unlock(struct snd_seq_queue *q) +{ + unsigned long flags; + + spin_lock_irqsave(&q->owner_lock, flags); + q->klocked = 0; + spin_unlock_irqrestore(&q->owner_lock, flags); +} + +/* exported - only checking permission */ +int snd_seq_queue_check_access(int queueid, int client) +{ + struct snd_seq_queue *q = queueptr(queueid); + int access_ok; + unsigned long flags; + + if (! q) + return 0; + spin_lock_irqsave(&q->owner_lock, flags); + access_ok = check_access(q, client); + spin_unlock_irqrestore(&q->owner_lock, flags); + queuefree(q); + return access_ok; +} + +/*----------------------------------------------------------------*/ + +/* + * change queue's owner and permission + */ +int snd_seq_queue_set_owner(int queueid, int client, int locked) +{ + struct snd_seq_queue *q = queueptr(queueid); + unsigned long flags; + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + spin_lock_irqsave(&q->owner_lock, flags); + q->locked = locked ? 1 : 0; + q->owner = client; + spin_unlock_irqrestore(&q->owner_lock, flags); + queue_access_unlock(q); + queuefree(q); + + return 0; +} + + +/*----------------------------------------------------------------*/ + +/* open timer - + * q->use mutex should be down before calling this function to avoid + * confliction with snd_seq_queue_use() + */ +int snd_seq_queue_timer_open(int queueid) +{ + int result = 0; + struct snd_seq_queue *queue; + struct snd_seq_timer *tmr; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + tmr = queue->timer; + result = snd_seq_timer_open(queue); + if (result < 0) { + snd_seq_timer_defaults(tmr); + result = snd_seq_timer_open(queue); + } + queuefree(queue); + return result; +} + +/* close timer - + * q->use mutex should be down before calling this function + */ +int snd_seq_queue_timer_close(int queueid) +{ + struct snd_seq_queue *queue; + int result = 0; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + snd_seq_timer_close(queue); + queuefree(queue); + return result; +} + +/* change queue tempo and ppq */ +int snd_seq_queue_timer_set_tempo(int queueid, int client, + struct snd_seq_queue_tempo *info) +{ + struct snd_seq_queue *q = queueptr(queueid); + int result; + + if (q == NULL) + return -EINVAL; + if (! queue_access_lock(q, client)) { + queuefree(q); + return -EPERM; + } + + result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq); + if (result >= 0 && info->skew_base > 0) + result = snd_seq_timer_set_skew(q->timer, info->skew_value, + info->skew_base); + queue_access_unlock(q); + queuefree(q); + return result; +} + +/* use or unuse this queue */ +static void queue_use(struct snd_seq_queue *queue, int client, int use) +{ + if (use) { + if (!test_and_set_bit(client, queue->clients_bitmap)) + queue->clients++; + } else { + if (test_and_clear_bit(client, queue->clients_bitmap)) + queue->clients--; + } + if (queue->clients) { + if (use && queue->clients == 1) + snd_seq_timer_defaults(queue->timer); + snd_seq_timer_open(queue); + } else { + snd_seq_timer_close(queue); + } +} + +/* use or unuse this queue - + * if it is the first client, starts the timer. + * if it is not longer used by any clients, stop the timer. + */ +int snd_seq_queue_use(int queueid, int client, int use) +{ + struct snd_seq_queue *queue; + + queue = queueptr(queueid); + if (queue == NULL) + return -EINVAL; + mutex_lock(&queue->timer_mutex); + queue_use(queue, client, use); + mutex_unlock(&queue->timer_mutex); + queuefree(queue); + return 0; +} + +/* + * check if queue is used by the client + * return negative value if the queue is invalid. + * return 0 if not used, 1 if used. + */ +int snd_seq_queue_is_used(int queueid, int client) +{ + struct snd_seq_queue *q; + int result; + + q = queueptr(queueid); + if (q == NULL) + return -EINVAL; /* invalid queue */ + result = test_bit(client, q->clients_bitmap) ? 1 : 0; + queuefree(q); + return result; +} + + +/*----------------------------------------------------------------*/ + +/* final stage notification - + * remove cells for no longer exist client (for non-owned queue) + * or delete this queue (for owned queue) + */ +void snd_seq_queue_client_leave(int client) +{ + int i; + struct snd_seq_queue *q; + + /* delete own queues from queue list */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queue_list_remove(i, client); + if (q) + queue_delete(q); + } + + /* remove cells from existing queues - + * they are not owned by this client + */ + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + if (test_bit(client, q->clients_bitmap)) { + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + snd_seq_queue_use(q->queue, client, 0); + } + queuefree(q); + } +} + + + +/*----------------------------------------------------------------*/ + +/* remove cells from all queues */ +void snd_seq_queue_client_leave_cells(int client) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + snd_seq_prioq_leave(q->tickq, client, 0); + snd_seq_prioq_leave(q->timeq, client, 0); + queuefree(q); + } +} + +/* remove cells based on flush criteria */ +void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info) +{ + int i; + struct snd_seq_queue *q; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + if (test_bit(client, q->clients_bitmap) && + (! (info->remove_mode & SNDRV_SEQ_REMOVE_DEST) || + q->queue == info->queue)) { + snd_seq_prioq_remove_events(q->tickq, client, info); + snd_seq_prioq_remove_events(q->timeq, client, info); + } + queuefree(q); + } +} + +/*----------------------------------------------------------------*/ + +/* + * send events to all subscribed ports + */ +static void queue_broadcast_event(struct snd_seq_queue *q, struct snd_seq_event *ev, + int atomic, int hop) +{ + struct snd_seq_event sev; + + sev = *ev; + + sev.flags = SNDRV_SEQ_TIME_STAMP_TICK|SNDRV_SEQ_TIME_MODE_ABS; + sev.time.tick = q->timer->tick.cur_tick; + sev.queue = q->queue; + sev.data.queue.queue = q->queue; + + /* broadcast events from Timer port */ + sev.source.client = SNDRV_SEQ_CLIENT_SYSTEM; + sev.source.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + sev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + snd_seq_kernel_client_dispatch(SNDRV_SEQ_CLIENT_SYSTEM, &sev, atomic, hop); +} + +/* + * process a received queue-control event. + * this function is exported for seq_sync.c. + */ +static void snd_seq_queue_process_event(struct snd_seq_queue *q, + struct snd_seq_event *ev, + int atomic, int hop) +{ + switch (ev->type) { + case SNDRV_SEQ_EVENT_START: + snd_seq_prioq_leave(q->tickq, ev->source.client, 1); + snd_seq_prioq_leave(q->timeq, ev->source.client, 1); + if (! snd_seq_timer_start(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_CONTINUE: + if (! snd_seq_timer_continue(q->timer)) + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_STOP: + snd_seq_timer_stop(q->timer); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_TEMPO: + snd_seq_timer_set_tempo(q->timer, ev->data.queue.param.value); + queue_broadcast_event(q, ev, atomic, hop); + break; + + case SNDRV_SEQ_EVENT_SETPOS_TICK: + if (snd_seq_timer_set_position_tick(q->timer, ev->data.queue.param.time.tick) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + + case SNDRV_SEQ_EVENT_SETPOS_TIME: + if (snd_seq_timer_set_position_time(q->timer, ev->data.queue.param.time.time) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + case SNDRV_SEQ_EVENT_QUEUE_SKEW: + if (snd_seq_timer_set_skew(q->timer, + ev->data.queue.param.skew.value, + ev->data.queue.param.skew.base) == 0) { + queue_broadcast_event(q, ev, atomic, hop); + } + break; + } +} + + +/* + * Queue control via timer control port: + * this function is exported as a callback of timer port. + */ +int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop) +{ + struct snd_seq_queue *q; + + if (snd_BUG_ON(!ev)) + return -EINVAL; + q = queueptr(ev->data.queue.queue); + + if (q == NULL) + return -EINVAL; + + if (! queue_access_lock(q, ev->source.client)) { + queuefree(q); + return -EPERM; + } + + snd_seq_queue_process_event(q, ev, atomic, hop); + + queue_access_unlock(q); + queuefree(q); + return 0; +} + + +/*----------------------------------------------------------------*/ + +#ifdef CONFIG_SND_PROC_FS +/* exported to seq_info.c */ +void snd_seq_info_queues_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int i, bpm; + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + bool locked; + int owner; + + for (i = 0; i < SNDRV_SEQ_MAX_QUEUES; i++) { + q = queueptr(i); + if (!q) + continue; + + tmr = q->timer; + if (tmr->tempo) + bpm = 60000000 / tmr->tempo; + else + bpm = 0; + + spin_lock_irq(&q->owner_lock); + locked = q->locked; + owner = q->owner; + spin_unlock_irq(&q->owner_lock); + + snd_iprintf(buffer, "queue %d: [%s]\n", q->queue, q->name); + snd_iprintf(buffer, "owned by client : %d\n", owner); + snd_iprintf(buffer, "lock status : %s\n", locked ? "Locked" : "Free"); + snd_iprintf(buffer, "queued time events : %d\n", snd_seq_prioq_avail(q->timeq)); + snd_iprintf(buffer, "queued tick events : %d\n", snd_seq_prioq_avail(q->tickq)); + snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped"); + snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq); + snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo); + snd_iprintf(buffer, "current BPM : %d\n", bpm); + snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec); + snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick); + snd_iprintf(buffer, "\n"); + queuefree(q); + } +} +#endif /* CONFIG_SND_PROC_FS */ + diff --git a/sound/core/seq/seq_queue.h b/sound/core/seq/seq_queue.h new file mode 100644 index 0000000000..c69105dc1a --- /dev/null +++ b/sound/core/seq/seq_queue.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Queue handling + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_QUEUE_H +#define __SND_SEQ_QUEUE_H + +#include "seq_memory.h" +#include "seq_prioq.h" +#include "seq_timer.h" +#include "seq_lock.h" +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/bitops.h> + +#define SEQ_QUEUE_NO_OWNER (-1) + +struct snd_seq_queue { + int queue; /* queue number */ + + char name[64]; /* name of this queue */ + + struct snd_seq_prioq *tickq; /* midi tick event queue */ + struct snd_seq_prioq *timeq; /* real-time event queue */ + + struct snd_seq_timer *timer; /* time keeper for this queue */ + int owner; /* client that 'owns' the timer */ + bool locked; /* timer is only accesibble by owner if set */ + bool klocked; /* kernel lock (after START) */ + bool check_again; /* concurrent access happened during check */ + bool check_blocked; /* queue being checked */ + + unsigned int flags; /* status flags */ + unsigned int info_flags; /* info for sync */ + + spinlock_t owner_lock; + spinlock_t check_lock; + + /* clients which uses this queue (bitmap) */ + DECLARE_BITMAP(clients_bitmap, SNDRV_SEQ_MAX_CLIENTS); + unsigned int clients; /* users of this queue */ + struct mutex timer_mutex; + + snd_use_lock_t use_lock; +}; + + +/* get the number of current queues */ +int snd_seq_queue_get_cur_queues(void); + +/* delete queues */ +void snd_seq_queues_delete(void); + + +/* create new queue (constructor) */ +struct snd_seq_queue *snd_seq_queue_alloc(int client, int locked, unsigned int flags); + +/* delete queue (destructor) */ +int snd_seq_queue_delete(int client, int queueid); + +/* final stage */ +void snd_seq_queue_client_leave(int client); + +/* enqueue a event received from one the clients */ +int snd_seq_enqueue_event(struct snd_seq_event_cell *cell, int atomic, int hop); + +/* Remove events */ +void snd_seq_queue_client_leave_cells(int client); +void snd_seq_queue_remove_cells(int client, struct snd_seq_remove_events *info); + +/* return pointer to queue structure for specified id */ +struct snd_seq_queue *queueptr(int queueid); +/* unlock */ +#define queuefree(q) snd_use_lock_free(&(q)->use_lock) + +/* return the (first) queue matching with the specified name */ +struct snd_seq_queue *snd_seq_queue_find_name(char *name); + +/* check single queue and dispatch events */ +void snd_seq_check_queue(struct snd_seq_queue *q, int atomic, int hop); + +/* access to queue's parameters */ +int snd_seq_queue_check_access(int queueid, int client); +int snd_seq_queue_timer_set_tempo(int queueid, int client, struct snd_seq_queue_tempo *info); +int snd_seq_queue_set_owner(int queueid, int client, int locked); +int snd_seq_queue_set_locked(int queueid, int client, int locked); +int snd_seq_queue_timer_open(int queueid); +int snd_seq_queue_timer_close(int queueid); +int snd_seq_queue_use(int queueid, int client, int use); +int snd_seq_queue_is_used(int queueid, int client); + +int snd_seq_control_queue(struct snd_seq_event *ev, int atomic, int hop); + +#endif diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c new file mode 100644 index 0000000000..8026729019 --- /dev/null +++ b/sound/core/seq/seq_system.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer System services Client + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "seq_system.h" +#include "seq_timer.h" +#include "seq_queue.h" + +/* internal client that provide system services, access to timer etc. */ + +/* + * Port "Timer" + * - send tempo /start/stop etc. events to this port to manipulate the + * queue's timer. The queue address is specified in + * data.queue.queue. + * - this port supports subscription. The received timer events are + * broadcasted to all subscribed clients. The modified tempo + * value is stored on data.queue.value. + * The modifier client/port is not send. + * + * Port "Announce" + * - does not receive message + * - supports supscription. For each client or port attaching to or + * detaching from the system an announcement is send to the subscribed + * clients. + * + * Idea: the subscription mechanism might also work handy for distributing + * synchronisation and timing information. In this case we would ideally have + * a list of subscribers for each type of sync (time, tick), for each timing + * queue. + * + * NOTE: the queue to be started, stopped, etc. must be specified + * in data.queue.addr.queue field. queue is used only for + * scheduling, and no longer referred as affected queue. + * They are used only for timer broadcast (see above). + * -- iwai + */ + + +/* client id of our system client */ +static int sysclient = -1; + +/* port id numbers for this client */ +static int announce_port = -1; + + + +/* fill standard header data, source port & channel are filled in */ +static int setheader(struct snd_seq_event * ev, int client, int port) +{ + if (announce_port < 0) + return -ENODEV; + + memset(ev, 0, sizeof(struct snd_seq_event)); + + ev->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SNDRV_SEQ_EVENT_LENGTH_FIXED; + + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + + /* fill data */ + /*ev->data.addr.queue = SNDRV_SEQ_ADDRESS_UNKNOWN;*/ + ev->data.addr.client = client; + ev->data.addr.port = port; + + return 0; +} + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type) +{ + struct snd_seq_event ev; + + if (setheader(&ev, client, port) < 0) + return; + ev.type = type; + snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); +} +EXPORT_SYMBOL_GPL(snd_seq_system_broadcast); + +/* entry points for broadcasting system events */ +int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev) +{ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + ev->source.client = sysclient; + ev->source.port = announce_port; + ev->dest.client = client; + ev->dest.port = port; + return snd_seq_kernel_client_dispatch(sysclient, ev, 0, 0); +} + +/* call-back handler for timer events */ +static int event_input_timer(struct snd_seq_event * ev, int direct, void *private_data, int atomic, int hop) +{ + return snd_seq_control_queue(ev, atomic, hop); +} + +/* register our internal client */ +int __init snd_seq_system_client_init(void) +{ + struct snd_seq_port_callback pcallbacks; + struct snd_seq_port_info *port; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.event_input = event_input_timer; + + /* register client */ + sysclient = snd_seq_create_kernel_client(NULL, 0, "System"); + if (sysclient < 0) { + kfree(port); + return sysclient; + } + + /* register timer */ + strcpy(port->name, "Timer"); + port->capability = SNDRV_SEQ_PORT_CAP_WRITE; /* accept queue control */ + port->capability |= SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast */ + port->kernel = &pcallbacks; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_TIMER; + err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + if (err < 0) + goto error_port; + + /* register announcement port */ + strcpy(port->name, "Announce"); + port->capability = SNDRV_SEQ_PORT_CAP_READ|SNDRV_SEQ_PORT_CAP_SUBS_READ; /* for broadcast only */ + port->kernel = NULL; + port->type = 0; + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->addr.client = sysclient; + port->addr.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + err = snd_seq_kernel_client_ctl(sysclient, SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + if (err < 0) + goto error_port; + announce_port = port->addr.port; + + kfree(port); + return 0; + + error_port: + snd_seq_system_client_done(); + kfree(port); + return err; +} + + +/* unregister our internal client */ +void snd_seq_system_client_done(void) +{ + int oldsysclient = sysclient; + + if (oldsysclient >= 0) { + sysclient = -1; + announce_port = -1; + snd_seq_delete_kernel_client(oldsysclient); + } +} diff --git a/sound/core/seq/seq_system.h b/sound/core/seq/seq_system.h new file mode 100644 index 0000000000..4fe88ad403 --- /dev/null +++ b/sound/core/seq/seq_system.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer System Client + * Copyright (c) 1998 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_SYSTEM_H +#define __SND_SEQ_SYSTEM_H + +#include <sound/seq_kernel.h> + + +/* entry points for broadcasting system events */ +void snd_seq_system_broadcast(int client, int port, int type); + +#define snd_seq_system_client_ev_client_start(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_START) +#define snd_seq_system_client_ev_client_exit(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_EXIT) +#define snd_seq_system_client_ev_client_change(client) snd_seq_system_broadcast(client, 0, SNDRV_SEQ_EVENT_CLIENT_CHANGE) +#define snd_seq_system_client_ev_port_start(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_START) +#define snd_seq_system_client_ev_port_exit(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_EXIT) +#define snd_seq_system_client_ev_port_change(client, port) snd_seq_system_broadcast(client, port, SNDRV_SEQ_EVENT_PORT_CHANGE) + +int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev); + +/* register our internal client */ +int snd_seq_system_client_init(void); + +/* unregister our internal client */ +void snd_seq_system_client_done(void); + + +#endif diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c new file mode 100644 index 0000000000..9863be6fd4 --- /dev/null +++ b/sound/core/seq/seq_timer.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + * Jaroslav Kysela <perex@perex.cz> + */ + +#include <sound/core.h> +#include <linux/slab.h> +#include "seq_timer.h" +#include "seq_queue.h" +#include "seq_info.h" + +/* allowed sequencer timer frequencies, in Hz */ +#define MIN_FREQUENCY 10 +#define MAX_FREQUENCY 6250 +#define DEFAULT_FREQUENCY 1000 + +#define SKEW_BASE 0x10000 /* 16bit shift */ + +static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr) +{ + if (tmr->tempo < 1000000) + tmr->tick.resolution = (tmr->tempo * 1000) / tmr->ppq; + else { + /* might overflow.. */ + unsigned int s; + s = tmr->tempo % tmr->ppq; + s = (s * 1000) / tmr->ppq; + tmr->tick.resolution = (tmr->tempo / tmr->ppq) * 1000; + tmr->tick.resolution += s; + } + if (tmr->tick.resolution <= 0) + tmr->tick.resolution = 1; + snd_seq_timer_update_tick(&tmr->tick, 0); +} + +/* create new timer (constructor) */ +struct snd_seq_timer *snd_seq_timer_new(void) +{ + struct snd_seq_timer *tmr; + + tmr = kzalloc(sizeof(*tmr), GFP_KERNEL); + if (!tmr) + return NULL; + spin_lock_init(&tmr->lock); + + /* reset setup to defaults */ + snd_seq_timer_defaults(tmr); + + /* reset time */ + snd_seq_timer_reset(tmr); + + return tmr; +} + +/* delete timer (destructor) */ +void snd_seq_timer_delete(struct snd_seq_timer **tmr) +{ + struct snd_seq_timer *t = *tmr; + *tmr = NULL; + + if (t == NULL) { + pr_debug("ALSA: seq: snd_seq_timer_delete() called with NULL timer\n"); + return; + } + t->running = 0; + + /* reset time */ + snd_seq_timer_stop(t); + snd_seq_timer_reset(t); + + kfree(t); +} + +void snd_seq_timer_defaults(struct snd_seq_timer * tmr) +{ + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + /* setup defaults */ + tmr->ppq = 96; /* 96 PPQ */ + tmr->tempo = 500000; /* 120 BPM */ + snd_seq_timer_set_tick_resolution(tmr); + tmr->running = 0; + + tmr->type = SNDRV_SEQ_TIMER_ALSA; + tmr->alsa_id.dev_class = seq_default_timer_class; + tmr->alsa_id.dev_sclass = seq_default_timer_sclass; + tmr->alsa_id.card = seq_default_timer_card; + tmr->alsa_id.device = seq_default_timer_device; + tmr->alsa_id.subdevice = seq_default_timer_subdevice; + tmr->preferred_resolution = seq_default_timer_resolution; + + tmr->skew = tmr->skew_base = SKEW_BASE; + spin_unlock_irqrestore(&tmr->lock, flags); +} + +static void seq_timer_reset(struct snd_seq_timer *tmr) +{ + /* reset time & songposition */ + tmr->cur_time.tv_sec = 0; + tmr->cur_time.tv_nsec = 0; + + tmr->tick.cur_tick = 0; + tmr->tick.fraction = 0; +} + +void snd_seq_timer_reset(struct snd_seq_timer *tmr) +{ + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + seq_timer_reset(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); +} + + +/* called by timer interrupt routine. the period time since previous invocation is passed */ +static void snd_seq_timer_interrupt(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + unsigned long flags; + struct snd_seq_queue *q = timeri->callback_data; + struct snd_seq_timer *tmr; + + if (q == NULL) + return; + tmr = q->timer; + if (tmr == NULL) + return; + spin_lock_irqsave(&tmr->lock, flags); + if (!tmr->running) { + spin_unlock_irqrestore(&tmr->lock, flags); + return; + } + + resolution *= ticks; + if (tmr->skew != tmr->skew_base) { + /* FIXME: assuming skew_base = 0x10000 */ + resolution = (resolution >> 16) * tmr->skew + + (((resolution & 0xffff) * tmr->skew) >> 16); + } + + /* update timer */ + snd_seq_inc_time_nsec(&tmr->cur_time, resolution); + + /* calculate current tick */ + snd_seq_timer_update_tick(&tmr->tick, resolution); + + /* register actual time of this timer update */ + ktime_get_ts64(&tmr->last_update); + + spin_unlock_irqrestore(&tmr->lock, flags); + + /* check queues and dispatch events */ + snd_seq_check_queue(q, 1, 0); +} + +/* set current tempo */ +int snd_seq_timer_set_tempo(struct snd_seq_timer * tmr, int tempo) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tempo <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if ((unsigned int)tempo != tmr->tempo) { + tmr->tempo = tempo; + snd_seq_timer_set_tick_resolution(tmr); + } + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current tempo and ppq in a shot */ +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq) +{ + int changed; + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tempo <= 0 || ppq <= 0) + return -EINVAL; + spin_lock_irqsave(&tmr->lock, flags); + if (tmr->running && (ppq != tmr->ppq)) { + /* refuse to change ppq on running timers */ + /* because it will upset the song position (ticks) */ + spin_unlock_irqrestore(&tmr->lock, flags); + pr_debug("ALSA: seq: cannot change ppq of a running timer\n"); + return -EBUSY; + } + changed = (tempo != tmr->tempo) || (ppq != tmr->ppq); + tmr->tempo = tempo; + tmr->ppq = ppq; + if (changed) + snd_seq_timer_set_tick_resolution(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current tick position */ +int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, + snd_seq_tick_time_t position) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + spin_lock_irqsave(&tmr->lock, flags); + tmr->tick.cur_tick = position; + tmr->tick.fraction = 0; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set current real-time position */ +int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, + snd_seq_real_time_t position) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + snd_seq_sanity_real_time(&position); + spin_lock_irqsave(&tmr->lock, flags); + tmr->cur_time = position; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +/* set timer skew */ +int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, + unsigned int base) +{ + unsigned long flags; + + if (snd_BUG_ON(!tmr)) + return -EINVAL; + + /* FIXME */ + if (base != SKEW_BASE) { + pr_debug("ALSA: seq: invalid skew base 0x%x\n", base); + return -EINVAL; + } + spin_lock_irqsave(&tmr->lock, flags); + tmr->skew = skew; + spin_unlock_irqrestore(&tmr->lock, flags); + return 0; +} + +int snd_seq_timer_open(struct snd_seq_queue *q) +{ + struct snd_timer_instance *t; + struct snd_seq_timer *tmr; + char str[32]; + int err; + + tmr = q->timer; + if (snd_BUG_ON(!tmr)) + return -EINVAL; + if (tmr->timeri) + return -EBUSY; + sprintf(str, "sequencer queue %i", q->queue); + if (tmr->type != SNDRV_SEQ_TIMER_ALSA) /* standard ALSA timer */ + return -EINVAL; + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) + tmr->alsa_id.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + t = snd_timer_instance_new(str); + if (!t) + return -ENOMEM; + t->callback = snd_seq_timer_interrupt; + t->callback_data = q; + t->flags |= SNDRV_TIMER_IFLG_AUTO; + err = snd_timer_open(t, &tmr->alsa_id, q->queue); + if (err < 0 && tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_SLAVE) { + if (tmr->alsa_id.dev_class != SNDRV_TIMER_CLASS_GLOBAL || + tmr->alsa_id.device != SNDRV_TIMER_GLOBAL_SYSTEM) { + struct snd_timer_id tid; + memset(&tid, 0, sizeof(tid)); + tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL; + tid.dev_sclass = SNDRV_TIMER_SCLASS_SEQUENCER; + tid.card = -1; + tid.device = SNDRV_TIMER_GLOBAL_SYSTEM; + err = snd_timer_open(t, &tid, q->queue); + } + } + if (err < 0) { + pr_err("ALSA: seq fatal error: cannot create timer (%i)\n", err); + snd_timer_instance_free(t); + return err; + } + spin_lock_irq(&tmr->lock); + if (tmr->timeri) + err = -EBUSY; + else + tmr->timeri = t; + spin_unlock_irq(&tmr->lock); + if (err < 0) { + snd_timer_close(t); + snd_timer_instance_free(t); + return err; + } + return 0; +} + +int snd_seq_timer_close(struct snd_seq_queue *q) +{ + struct snd_seq_timer *tmr; + struct snd_timer_instance *t; + + tmr = q->timer; + if (snd_BUG_ON(!tmr)) + return -EINVAL; + spin_lock_irq(&tmr->lock); + t = tmr->timeri; + tmr->timeri = NULL; + spin_unlock_irq(&tmr->lock); + if (t) { + snd_timer_close(t); + snd_timer_instance_free(t); + } + return 0; +} + +static int seq_timer_stop(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (!tmr->running) + return 0; + tmr->running = 0; + snd_timer_pause(tmr->timeri); + return 0; +} + +int snd_seq_timer_stop(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_stop(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +static int initialize_timer(struct snd_seq_timer *tmr) +{ + struct snd_timer *t; + unsigned long freq; + + t = tmr->timeri->timer; + if (!t) + return -EINVAL; + + freq = tmr->preferred_resolution; + if (!freq) + freq = DEFAULT_FREQUENCY; + else if (freq < MIN_FREQUENCY) + freq = MIN_FREQUENCY; + else if (freq > MAX_FREQUENCY) + freq = MAX_FREQUENCY; + + tmr->ticks = 1; + if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE)) { + unsigned long r = snd_timer_resolution(tmr->timeri); + if (r) { + tmr->ticks = (unsigned int)(1000000000uL / (r * freq)); + if (! tmr->ticks) + tmr->ticks = 1; + } + } + tmr->initialized = 1; + return 0; +} + +static int seq_timer_start(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + seq_timer_stop(tmr); + seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + ktime_get_ts64(&tmr->last_update); + return 0; +} + +int snd_seq_timer_start(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_start(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +static int seq_timer_continue(struct snd_seq_timer *tmr) +{ + if (! tmr->timeri) + return -EINVAL; + if (tmr->running) + return -EBUSY; + if (! tmr->initialized) { + seq_timer_reset(tmr); + if (initialize_timer(tmr) < 0) + return -EINVAL; + } + snd_timer_start(tmr->timeri, tmr->ticks); + tmr->running = 1; + ktime_get_ts64(&tmr->last_update); + return 0; +} + +int snd_seq_timer_continue(struct snd_seq_timer *tmr) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&tmr->lock, flags); + err = seq_timer_continue(tmr); + spin_unlock_irqrestore(&tmr->lock, flags); + return err; +} + +/* return current 'real' time. use timeofday() to get better granularity. */ +snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr, + bool adjust_ktime) +{ + snd_seq_real_time_t cur_time; + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + cur_time = tmr->cur_time; + if (adjust_ktime && tmr->running) { + struct timespec64 tm; + + ktime_get_ts64(&tm); + tm = timespec64_sub(tm, tmr->last_update); + cur_time.tv_nsec += tm.tv_nsec; + cur_time.tv_sec += tm.tv_sec; + snd_seq_sanity_real_time(&cur_time); + } + spin_unlock_irqrestore(&tmr->lock, flags); + return cur_time; +} + +/* TODO: use interpolation on tick queue (will only be useful for very + high PPQ values) */ +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr) +{ + snd_seq_tick_time_t cur_tick; + unsigned long flags; + + spin_lock_irqsave(&tmr->lock, flags); + cur_tick = tmr->tick.cur_tick; + spin_unlock_irqrestore(&tmr->lock, flags); + return cur_tick; +} + + +#ifdef CONFIG_SND_PROC_FS +/* exported to seq_info.c */ +void snd_seq_info_timer_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int idx; + struct snd_seq_queue *q; + struct snd_seq_timer *tmr; + struct snd_timer_instance *ti; + unsigned long resolution; + + for (idx = 0; idx < SNDRV_SEQ_MAX_QUEUES; idx++) { + q = queueptr(idx); + if (q == NULL) + continue; + mutex_lock(&q->timer_mutex); + tmr = q->timer; + if (!tmr) + goto unlock; + ti = tmr->timeri; + if (!ti) + goto unlock; + snd_iprintf(buffer, "Timer for queue %i : %s\n", q->queue, ti->timer->name); + resolution = snd_timer_resolution(ti) * tmr->ticks; + snd_iprintf(buffer, " Period time : %lu.%09lu\n", resolution / 1000000000, resolution % 1000000000); + snd_iprintf(buffer, " Skew : %u / %u\n", tmr->skew, tmr->skew_base); +unlock: + mutex_unlock(&q->timer_mutex); + queuefree(q); + } +} +#endif /* CONFIG_SND_PROC_FS */ + diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h new file mode 100644 index 0000000000..4bec57df81 --- /dev/null +++ b/sound/core/seq/seq_timer.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ALSA sequencer Timer + * Copyright (c) 1998-1999 by Frank van de Pol <fvdpol@coil.demon.nl> + */ +#ifndef __SND_SEQ_TIMER_H +#define __SND_SEQ_TIMER_H + +#include <sound/timer.h> +#include <sound/seq_kernel.h> + +struct snd_seq_timer_tick { + snd_seq_tick_time_t cur_tick; /* current tick */ + unsigned long resolution; /* time per tick in nsec */ + unsigned long fraction; /* current time per tick in nsec */ +}; + +struct snd_seq_timer { + /* ... tempo / offset / running state */ + + unsigned int running:1, /* running state of queue */ + initialized:1; /* timer is initialized */ + + unsigned int tempo; /* current tempo, us/tick */ + int ppq; /* time resolution, ticks/quarter */ + + snd_seq_real_time_t cur_time; /* current time */ + struct snd_seq_timer_tick tick; /* current tick */ + int tick_updated; + + int type; /* timer type */ + struct snd_timer_id alsa_id; /* ALSA's timer ID */ + struct snd_timer_instance *timeri; /* timer instance */ + unsigned int ticks; + unsigned long preferred_resolution; /* timer resolution, ticks/sec */ + + unsigned int skew; + unsigned int skew_base; + + struct timespec64 last_update; /* time of last clock update, used for interpolation */ + + spinlock_t lock; +}; + + +/* create new timer (constructor) */ +struct snd_seq_timer *snd_seq_timer_new(void); + +/* delete timer (destructor) */ +void snd_seq_timer_delete(struct snd_seq_timer **tmr); + +/* */ +static inline void snd_seq_timer_update_tick(struct snd_seq_timer_tick *tick, + unsigned long resolution) +{ + if (tick->resolution > 0) { + tick->fraction += resolution; + tick->cur_tick += (unsigned int)(tick->fraction / tick->resolution); + tick->fraction %= tick->resolution; + } +} + + +/* compare timestamp between events */ +/* return 1 if a >= b; otherwise return 0 */ +static inline int snd_seq_compare_tick_time(snd_seq_tick_time_t *a, snd_seq_tick_time_t *b) +{ + /* compare ticks */ + return (*a >= *b); +} + +static inline int snd_seq_compare_real_time(snd_seq_real_time_t *a, snd_seq_real_time_t *b) +{ + /* compare real time */ + if (a->tv_sec > b->tv_sec) + return 1; + if ((a->tv_sec == b->tv_sec) && (a->tv_nsec >= b->tv_nsec)) + return 1; + return 0; +} + + +static inline void snd_seq_sanity_real_time(snd_seq_real_time_t *tm) +{ + while (tm->tv_nsec >= 1000000000) { + /* roll-over */ + tm->tv_nsec -= 1000000000; + tm->tv_sec++; + } +} + + +/* increment timestamp */ +static inline void snd_seq_inc_real_time(snd_seq_real_time_t *tm, snd_seq_real_time_t *inc) +{ + tm->tv_sec += inc->tv_sec; + tm->tv_nsec += inc->tv_nsec; + snd_seq_sanity_real_time(tm); +} + +static inline void snd_seq_inc_time_nsec(snd_seq_real_time_t *tm, unsigned long nsec) +{ + tm->tv_nsec += nsec; + snd_seq_sanity_real_time(tm); +} + +/* called by timer isr */ +struct snd_seq_queue; +int snd_seq_timer_open(struct snd_seq_queue *q); +int snd_seq_timer_close(struct snd_seq_queue *q); +int snd_seq_timer_midi_open(struct snd_seq_queue *q); +int snd_seq_timer_midi_close(struct snd_seq_queue *q); +void snd_seq_timer_defaults(struct snd_seq_timer *tmr); +void snd_seq_timer_reset(struct snd_seq_timer *tmr); +int snd_seq_timer_stop(struct snd_seq_timer *tmr); +int snd_seq_timer_start(struct snd_seq_timer *tmr); +int snd_seq_timer_continue(struct snd_seq_timer *tmr); +int snd_seq_timer_set_tempo(struct snd_seq_timer *tmr, int tempo); +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq); +int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, snd_seq_tick_time_t position); +int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, snd_seq_real_time_t position); +int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, unsigned int base); +snd_seq_real_time_t snd_seq_timer_get_cur_time(struct snd_seq_timer *tmr, + bool adjust_ktime); +snd_seq_tick_time_t snd_seq_timer_get_cur_tick(struct snd_seq_timer *tmr); + +extern int seq_default_timer_class; +extern int seq_default_timer_sclass; +extern int seq_default_timer_card; +extern int seq_default_timer_device; +extern int seq_default_timer_subdevice; +extern int seq_default_timer_resolution; + +#endif diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c new file mode 100644 index 0000000000..2db371d799 --- /dev/null +++ b/sound/core/seq/seq_ump_client.c @@ -0,0 +1,562 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ALSA sequencer binding for UMP device */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/module.h> +#include <asm/byteorder.h> +#include <sound/core.h> +#include <sound/ump.h> +#include <sound/seq_kernel.h> +#include <sound/seq_device.h> +#include "seq_clientmgr.h" +#include "seq_system.h" + +struct seq_ump_client; +struct seq_ump_group; + +enum { + STR_IN = SNDRV_RAWMIDI_STREAM_INPUT, + STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT +}; + +/* object per UMP group; corresponding to a sequencer port */ +struct seq_ump_group { + int group; /* group index (0-based) */ + unsigned int dir_bits; /* directions */ + bool active; /* activeness */ + char name[64]; /* seq port name */ +}; + +/* context for UMP input parsing, per EP */ +struct seq_ump_input_buffer { + unsigned char len; /* total length in words */ + unsigned char pending; /* pending words */ + unsigned char type; /* parsed UMP packet type */ + unsigned char group; /* parsed UMP packet group */ + u32 buf[4]; /* incoming UMP packet */ +}; + +/* sequencer client, per UMP EP (rawmidi) */ +struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ + struct seq_ump_input_buffer input; /* input parser context */ + struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ +}; + +/* number of 32bit words for each UMP message type */ +static unsigned char ump_packet_words[0x10] = { + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 +}; + +/* conversion between UMP group and seq port; + * assume the port number is equal with UMP group number (1-based) + */ +static unsigned char ump_group_to_seq_port(unsigned char group) +{ + return group + 1; +} + +/* process the incoming rawmidi stream */ +static void seq_ump_input_receive(struct snd_ump_endpoint *ump, + const u32 *val, int words) +{ + struct seq_ump_client *client = ump->seq_client; + struct snd_seq_ump_event ev = {}; + + if (!client->opened[STR_IN]) + return; + + if (ump_is_groupless_msg(ump_message_type(*val))) + ev.source.port = 0; /* UMP EP port */ + else + ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + ev.flags = SNDRV_SEQ_EVENT_UMP; + memcpy(ev.ump, val, words << 2); + snd_seq_kernel_client_dispatch(client->seq_client, + (struct snd_seq_event *)&ev, + true, 0); +} + +/* process an input sequencer event; only deal with UMP types */ +static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct seq_ump_client *client = private_data; + struct snd_rawmidi_substream *substream; + struct snd_seq_ump_event *ump_ev; + unsigned char type; + int len; + + substream = client->out_rfile.output; + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) + return 0; /* invalid event, skip */ + ump_ev = (struct snd_seq_ump_event *)ev; + type = ump_message_type(ump_ev->ump[0]); + len = ump_packet_words[type]; + if (len > 4) + return 0; // invalid - skip + snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2); + return 0; +} + +/* open the rawmidi */ +static int seq_ump_client_open(struct seq_ump_client *client, int dir) +{ + struct snd_ump_endpoint *ump = client->ump; + int err = 0; + + mutex_lock(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, + &client->out_rfile); + if (err < 0) + goto unlock; + } + client->opened[dir]++; + unlock: + mutex_unlock(&ump->open_mutex); + return err; +} + +/* close the rawmidi */ +static int seq_ump_client_close(struct seq_ump_client *client, int dir) +{ + struct snd_ump_endpoint *ump = client->ump; + + mutex_lock(&ump->open_mutex); + if (!--client->opened[dir]) + if (dir == STR_OUT) + snd_rawmidi_kernel_release(&client->out_rfile); + mutex_unlock(&ump->open_mutex); + return 0; +} + +/* sequencer subscription ops for each client */ +static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_open(client, STR_IN); +} + +static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_close(client, STR_IN); +} + +static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_open(client, STR_OUT); +} + +static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_close(client, STR_OUT); +} + +/* fill port_info from the given UMP EP and group info */ +static void fill_port_info(struct snd_seq_port_info *port, + struct seq_ump_client *client, + struct seq_ump_group *group) +{ + unsigned int rawmidi_info = client->ump->core.info_flags; + + port->addr.client = client->seq_client; + port->addr.port = ump_group_to_seq_port(group->group); + port->capability = 0; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SYNC_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) + port->capability |= SNDRV_SEQ_PORT_CAP_READ | + SNDRV_SEQ_PORT_CAP_SYNC_READ | + SNDRV_SEQ_PORT_CAP_SUBS_READ; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + if (group->dir_bits & (1 << STR_IN)) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (group->dir_bits & (1 << STR_OUT)) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + port->ump_group = group->group + 1; + if (!group->active) + port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE; + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_MIDI_UMP | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + if (*group->name) + snprintf(port->name, sizeof(port->name), "Group %d (%.53s)", + group->group + 1, group->name); + else + sprintf(port->name, "Group %d", group->group + 1); +} + +/* create a new sequencer port per UMP group */ +static int seq_ump_group_init(struct seq_ump_client *client, int group_index) +{ + struct seq_ump_group *group = &client->groups[group_index]; + struct snd_seq_port_info *port; + struct snd_seq_port_callback pcallbacks; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + err = -ENOMEM; + goto error; + } + + fill_port_info(port, client, group); + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = client; + pcallbacks.subscribe = seq_ump_subscribe; + pcallbacks.unsubscribe = seq_ump_unsubscribe; + pcallbacks.use = seq_ump_use; + pcallbacks.unuse = seq_ump_unuse; + pcallbacks.event_input = seq_ump_process_event; + port->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + error: + kfree(port); + return err; +} + +/* update the sequencer ports; called from notify_fb_change callback */ +static void update_port_infos(struct seq_ump_client *client) +{ + struct snd_seq_port_info *old, *new; + int i, err; + + old = kzalloc(sizeof(*old), GFP_KERNEL); + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!old || !new) + goto error; + + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { + old->addr.client = client->seq_client; + old->addr.port = i; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_GET_PORT_INFO, + old); + if (err < 0) + goto error; + fill_port_info(new, client, &client->groups[i]); + if (old->capability == new->capability && + !strcmp(old->name, new->name)) + continue; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_SET_PORT_INFO, + new); + if (err < 0) + goto error; + /* notify to system port */ + snd_seq_system_client_ev_port_change(client->seq_client, i); + } + error: + kfree(new); + kfree(old); +} + +/* update dir_bits and active flag for all groups in the client */ +static void update_group_attrs(struct seq_ump_client *client) +{ + struct snd_ump_block *fb; + struct seq_ump_group *group; + int i; + + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { + group = &client->groups[i]; + *group->name = 0; + group->dir_bits = 0; + group->active = 0; + group->group = i; + } + + list_for_each_entry(fb, &client->ump->block_list, list) { + if (fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS) + break; + group = &client->groups[fb->info.first_group]; + for (i = 0; i < fb->info.num_groups; i++, group++) { + if (fb->info.active) + group->active = 1; + switch (fb->info.direction) { + case SNDRV_UMP_DIR_INPUT: + group->dir_bits |= (1 << STR_IN); + break; + case SNDRV_UMP_DIR_OUTPUT: + group->dir_bits |= (1 << STR_OUT); + break; + case SNDRV_UMP_DIR_BIDIRECTION: + group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN); + break; + } + if (!*fb->info.name) + continue; + if (!*group->name) { + /* store the first matching name */ + strscpy(group->name, fb->info.name, + sizeof(group->name)); + } else { + /* when overlapping, concat names */ + strlcat(group->name, ", ", sizeof(group->name)); + strlcat(group->name, fb->info.name, + sizeof(group->name)); + } + } + } +} + +/* create a UMP Endpoint port */ +static int create_ump_endpoint_port(struct seq_ump_client *client) +{ + struct snd_seq_port_info *port; + struct snd_seq_port_callback pcallbacks; + unsigned int rawmidi_info = client->ump->core.info_flags; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->addr.client = client->seq_client; + port->addr.port = 0; /* fixed */ + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) { + port->capability |= SNDRV_SEQ_PORT_CAP_READ | + SNDRV_SEQ_PORT_CAP_SYNC_READ | + SNDRV_SEQ_PORT_CAP_SUBS_READ; + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) { + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SYNC_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + port->ump_group = 0; /* no associated group, no conversion */ + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + strcpy(port->name, "MIDI 2.0"); + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = client; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) { + pcallbacks.subscribe = seq_ump_subscribe; + pcallbacks.unsubscribe = seq_ump_unsubscribe; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) { + pcallbacks.use = seq_ump_use; + pcallbacks.unuse = seq_ump_unuse; + pcallbacks.event_input = seq_ump_process_event; + } + port->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + kfree(port); + return err; +} + +/* release the client resources */ +static void seq_ump_client_free(struct seq_ump_client *client) +{ + cancel_work_sync(&client->group_notify_work); + + if (client->seq_client >= 0) + snd_seq_delete_kernel_client(client->seq_client); + + client->ump->seq_ops = NULL; + client->ump->seq_client = NULL; + + kfree(client); +} + +/* update the MIDI version for the given client */ +static void setup_client_midi_version(struct seq_ump_client *client) +{ + struct snd_seq_client *cptr; + + cptr = snd_seq_kernel_client_get(client->seq_client); + if (!cptr) + return; + if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2) + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0; + else + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0; + snd_seq_kernel_client_put(cptr); +} + +/* set up client's group_filter bitmap */ +static void setup_client_group_filter(struct seq_ump_client *client) +{ + struct snd_seq_client *cptr; + unsigned int filter; + int p; + + cptr = snd_seq_kernel_client_get(client->seq_client); + if (!cptr) + return; + filter = ~(1U << 0); /* always allow groupless messages */ + for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) { + if (client->groups[p].active) + filter &= ~(1U << (p + 1)); + } + cptr->group_filter = filter; + snd_seq_kernel_client_put(cptr); +} + +/* UMP group change notification */ +static void handle_group_notify(struct work_struct *work) +{ + struct seq_ump_client *client = + container_of(work, struct seq_ump_client, group_notify_work); + + update_group_attrs(client); + update_port_infos(client); + setup_client_group_filter(client); +} + +/* UMP FB change notification */ +static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ + struct seq_ump_client *client = ump->seq_client; + + if (!client) + return -ENODEV; + schedule_work(&client->group_notify_work); + return 0; +} + +/* UMP protocol change notification; just update the midi_version field */ +static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump) +{ + if (!ump->seq_client) + return -ENODEV; + setup_client_midi_version(ump->seq_client); + return 0; +} + +static const struct snd_seq_ump_ops seq_ump_ops = { + .input_receive = seq_ump_input_receive, + .notify_fb_change = seq_ump_notify_fb_change, + .switch_protocol = seq_ump_switch_protocol, +}; + +/* create a sequencer client and ports for the given UMP endpoint */ +static int snd_seq_ump_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_ump_endpoint *ump = dev->private_data; + struct snd_card *card = dev->card; + struct seq_ump_client *client; + struct snd_ump_block *fb; + struct snd_seq_client *cptr; + int p, err; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + INIT_WORK(&client->group_notify_work, handle_group_notify); + client->ump = ump; + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, + ump->core.name); + if (client->seq_client < 0) { + err = client->seq_client; + goto error; + } + + client->ump_info[0] = &ump->info; + list_for_each_entry(fb, &ump->block_list, list) + client->ump_info[fb->info.block_id + 1] = &fb->info; + + setup_client_midi_version(client); + update_group_attrs(client); + + for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) { + err = seq_ump_group_init(client, p); + if (err < 0) + goto error; + } + + setup_client_group_filter(client); + + err = create_ump_endpoint_port(client); + if (err < 0) + goto error; + + cptr = snd_seq_kernel_client_get(client->seq_client); + if (!cptr) { + err = -EINVAL; + goto error; + } + cptr->ump_info = client->ump_info; + snd_seq_kernel_client_put(cptr); + + ump->seq_client = client; + ump->seq_ops = &seq_ump_ops; + return 0; + + error: + seq_ump_client_free(client); + return err; +} + +/* remove a sequencer client */ +static int snd_seq_ump_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_ump_endpoint *ump = dev->private_data; + + if (ump->seq_client) + seq_ump_client_free(ump->seq_client); + return 0; +} + +static struct snd_seq_driver seq_ump_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_ump_probe, + .remove = snd_seq_ump_remove, + }, + .id = SNDRV_SEQ_DEV_ID_UMP, + .argsize = 0, +}; + +module_snd_seq_driver(seq_ump_driver); + +MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi"); +MODULE_LICENSE("GPL"); diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c new file mode 100644 index 0000000000..b141024830 --- /dev/null +++ b/sound/core/seq/seq_ump_convert.c @@ -0,0 +1,1208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer event conversion between UMP and legacy clients + */ + +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <sound/core.h> +#include <sound/ump.h> +#include <sound/ump_msg.h> +#include "seq_ump_convert.h" + +/* + * Upgrade / downgrade value bits + */ +static u8 downscale_32_to_7bit(u32 src) +{ + return src >> 25; +} + +static u16 downscale_32_to_14bit(u32 src) +{ + return src >> 18; +} + +static u8 downscale_16_to_7bit(u16 src) +{ + return src >> 9; +} + +static u16 upscale_7_to_16bit(u8 src) +{ + u16 val, repeat; + + val = (u16)src << 9; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 3) | (repeat >> 3); +} + +static u32 upscale_7_to_32bit(u8 src) +{ + u32 val, repeat; + + val = src << 25; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 19) | (repeat << 13) | + (repeat << 7) | (repeat << 1) | (repeat >> 5); +} + +static u32 upscale_14_to_32bit(u16 src) +{ + u32 val, repeat; + + val = src << 18; + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +static unsigned char get_ump_group(struct snd_seq_client_port *port) +{ + return port->ump_group ? (port->ump_group - 1) : 0; +} + +/* create a UMP header */ +#define make_raw_ump(port, type) \ + ump_compose(type, get_ump_group(port), 0, 0) + +/* + * UMP -> MIDI1 sequencer event + */ + +/* MIDI 1.0 CVM */ + +/* encode note event */ +static void ump_midi1_to_note_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.note.channel = val->note.channel; + ev->data.note.note = val->note.note; + ev->data.note.velocity = val->note.velocity; +} + +/* encode one parameter controls */ +static void ump_midi1_to_ctrl_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->caf.channel; + ev->data.control.value = val->caf.data; +} + +/* encode pitch wheel change */ +static void ump_midi1_to_pitchbend_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->pb.channel; + ev->data.control.value = (val->pb.data_msb << 7) | val->pb.data_lsb; + ev->data.control.value -= 8192; +} + +/* encode midi control change */ +static void ump_midi1_to_cc_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->cc.channel; + ev->data.control.param = val->cc.index; + ev->data.control.value = val->cc.data; +} + +/* Encoding MIDI 1.0 UMP packet */ +struct seq_ump_midi1_to_ev { + int seq_type; + void (*encode)(const union snd_ump_midi1_msg *val, struct snd_seq_event *ev); +}; + +/* Encoders for MIDI1 status 0x80-0xe0 */ +static struct seq_ump_midi1_to_ev midi1_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi1_to_note_ev}, /* 0x80 */ + {SNDRV_SEQ_EVENT_NOTEON, ump_midi1_to_note_ev}, /* 0x90 */ + {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi1_to_note_ev}, /* 0xa0 */ + {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi1_to_cc_ev}, /* 0xb0 */ + {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi1_to_ctrl_ev}, /* 0xc0 */ + {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi1_to_ctrl_ev}, /* 0xd0 */ + {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi1_to_pitchbend_ev}, /* 0xe0 */ +}; + +static int cvt_ump_midi1_to_event(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->note.status; + + if (status < 0x8 || status > 0xe) + return 0; /* invalid - skip */ + status -= 8; + ev->type = midi1_msg_encoders[status].seq_type; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + midi1_msg_encoders[status].encode(val, ev); + return 1; +} + +/* MIDI System message */ + +/* encode one parameter value*/ +static void ump_system_to_one_param_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.value = val->system.parm1; +} + +/* encode song position */ +static void ump_system_to_songpos_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.value = (val->system.parm1 << 7) | val->system.parm2; +} + +/* Encoders for 0xf0 - 0xff */ +static struct seq_ump_midi1_to_ev system_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */ + {SNDRV_SEQ_EVENT_QFRAME, ump_system_to_one_param_ev}, /* 0xf1 */ + {SNDRV_SEQ_EVENT_SONGPOS, ump_system_to_songpos_ev}, /* 0xf2 */ + {SNDRV_SEQ_EVENT_SONGSEL, ump_system_to_one_param_ev}, /* 0xf3 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf4 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf5 */ + {SNDRV_SEQ_EVENT_TUNE_REQUEST, NULL}, /* 0xf6 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf7 */ + {SNDRV_SEQ_EVENT_CLOCK, NULL}, /* 0xf8 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf9 */ + {SNDRV_SEQ_EVENT_START, NULL}, /* 0xfa */ + {SNDRV_SEQ_EVENT_CONTINUE, NULL}, /* 0xfb */ + {SNDRV_SEQ_EVENT_STOP, NULL}, /* 0xfc */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xfd */ + {SNDRV_SEQ_EVENT_SENSING, NULL}, /* 0xfe */ + {SNDRV_SEQ_EVENT_RESET, NULL}, /* 0xff */ +}; + +static int cvt_ump_system_to_event(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->system.status; + + if ((status & 0xf0) != UMP_MIDI1_MSG_REALTIME) + return 0; /* invalid status - skip */ + status &= 0x0f; + ev->type = system_msg_encoders[status].seq_type; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; + if (system_msg_encoders[status].encode) + system_msg_encoders[status].encode(val, ev); + return 1; +} + +/* MIDI 2.0 CVM */ + +/* encode note event */ +static int ump_midi2_to_note_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.note.channel = val->note.channel; + ev->data.note.note = val->note.note; + ev->data.note.velocity = downscale_16_to_7bit(val->note.velocity); + /* correct note-on velocity 0 to 1; + * it's no longer equivalent as not-off for MIDI 2.0 + */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON && + !ev->data.note.velocity) + ev->data.note.velocity = 1; + return 1; +} + +/* encode pitch wheel change */ +static int ump_midi2_to_pitchbend_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->pb.channel; + ev->data.control.value = downscale_32_to_14bit(val->pb.data); + ev->data.control.value -= 8192; + return 1; +} + +/* encode midi control change */ +static int ump_midi2_to_cc_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->cc.channel; + ev->data.control.param = val->cc.index; + ev->data.control.value = downscale_32_to_7bit(val->cc.data); + return 1; +} + +/* encode midi program change */ +static int ump_midi2_to_pgm_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + int size = 1; + + ev->data.control.channel = val->pg.channel; + if (val->pg.bank_valid) { + ev->type = SNDRV_SEQ_EVENT_CONTROL14; + ev->data.control.param = UMP_CC_BANK_SELECT; + ev->data.control.value = (val->pg.bank_msb << 7) | val->pg.bank_lsb; + ev[1] = ev[0]; + ev++; + ev->type = SNDRV_SEQ_EVENT_PGMCHANGE; + size = 2; + } + ev->data.control.value = val->pg.program; + return size; +} + +/* encode one parameter controls */ +static int ump_midi2_to_ctrl_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->caf.channel; + ev->data.control.value = downscale_32_to_7bit(val->caf.data); + return 1; +} + +/* encode RPN/NRPN */ +static int ump_midi2_to_rpn_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->rpn.channel; + ev->data.control.param = (val->rpn.bank << 7) | val->rpn.index; + ev->data.control.value = downscale_32_to_14bit(val->rpn.data); + return 1; +} + +/* Encoding MIDI 2.0 UMP Packet */ +struct seq_ump_midi2_to_ev { + int seq_type; + int (*encode)(const union snd_ump_midi2_msg *val, struct snd_seq_event *ev); +}; + +/* Encoders for MIDI2 status 0x00-0xf0 */ +static struct seq_ump_midi2_to_ev midi2_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x00 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x10 */ + {SNDRV_SEQ_EVENT_REGPARAM, ump_midi2_to_rpn_ev}, /* 0x20 */ + {SNDRV_SEQ_EVENT_NONREGPARAM, ump_midi2_to_rpn_ev}, /* 0x30 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x40 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x50 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x60 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x70 */ + {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi2_to_note_ev}, /* 0x80 */ + {SNDRV_SEQ_EVENT_NOTEON, ump_midi2_to_note_ev}, /* 0x90 */ + {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi2_to_note_ev}, /* 0xa0 */ + {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi2_to_cc_ev}, /* 0xb0 */ + {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi2_to_pgm_ev}, /* 0xc0 */ + {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi2_to_ctrl_ev}, /* 0xd0 */ + {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi2_to_pitchbend_ev}, /* 0xe0 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */ +}; + +static int cvt_ump_midi2_to_event(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->note.status; + + ev->type = midi2_msg_encoders[status].seq_type; + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* skip */ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + return midi2_msg_encoders[status].encode(val, ev); +} + +/* parse and compose for a sysex var-length event */ +static int cvt_ump_sysex7_to_event(const u32 *data, unsigned char *buf, + struct snd_seq_event *ev) +{ + unsigned char status; + unsigned char bytes; + u32 val; + int size = 0; + + val = data[0]; + status = ump_sysex_message_status(val); + bytes = ump_sysex_message_length(val); + if (bytes > 6) + return 0; // skip + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_START) { + buf[0] = UMP_MIDI1_MSG_SYSEX_START; + size = 1; + } + + if (bytes > 0) + buf[size++] = (val >> 8) & 0x7f; + if (bytes > 1) + buf[size++] = val & 0x7f; + val = data[1]; + if (bytes > 2) + buf[size++] = (val >> 24) & 0x7f; + if (bytes > 3) + buf[size++] = (val >> 16) & 0x7f; + if (bytes > 4) + buf[size++] = (val >> 8) & 0x7f; + if (bytes > 5) + buf[size++] = val & 0x7f; + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_END) + buf[size++] = UMP_MIDI1_MSG_SYSEX_END; + + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->data.ext.len = size; + ev->data.ext.ptr = buf; + return 1; +} + +/* convert UMP packet from MIDI 1.0 to MIDI 2.0 and deliver it */ +static int cvt_ump_midi1_to_midi2(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *__event, + int atomic, int hop) +{ + struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event; + struct snd_seq_ump_event ev_cvt; + const union snd_ump_midi1_msg *midi1 = (const union snd_ump_midi1_msg *)event->ump; + union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)ev_cvt.ump; + + ev_cvt = *event; + memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump)); + + midi2->note.type = UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE; + midi2->note.group = midi1->note.group; + midi2->note.status = midi1->note.status; + midi2->note.channel = midi1->note.channel; + switch (midi1->note.status) { + case UMP_MSG_STATUS_NOTE_ON: + case UMP_MSG_STATUS_NOTE_OFF: + midi2->note.note = midi1->note.note; + midi2->note.velocity = upscale_7_to_16bit(midi1->note.velocity); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi2->paf.note = midi1->paf.note; + midi2->paf.data = upscale_7_to_32bit(midi1->paf.data); + break; + case UMP_MSG_STATUS_CC: + midi2->cc.index = midi1->cc.index; + midi2->cc.data = upscale_7_to_32bit(midi1->cc.data); + break; + case UMP_MSG_STATUS_PROGRAM: + midi2->pg.program = midi1->pg.program; + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi2->caf.data = upscale_7_to_32bit(midi1->caf.data); + break; + case UMP_MSG_STATUS_PITCH_BEND: + midi2->pb.data = upscale_14_to_32bit((midi1->pb.data_msb << 7) | + midi1->pb.data_lsb); + break; + default: + return 0; + } + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); +} + +/* convert UMP packet from MIDI 2.0 to MIDI 1.0 and deliver it */ +static int cvt_ump_midi2_to_midi1(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *__event, + int atomic, int hop) +{ + struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi1_msg *midi1 = (union snd_ump_midi1_msg *)ev_cvt.ump; + const union snd_ump_midi2_msg *midi2 = (const union snd_ump_midi2_msg *)event->ump; + u16 v; + + ev_cvt = *event; + memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump)); + + midi1->note.type = UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE; + midi1->note.group = midi2->note.group; + midi1->note.status = midi2->note.status; + midi1->note.channel = midi2->note.channel; + switch (midi2->note.status << 4) { + case UMP_MSG_STATUS_NOTE_ON: + case UMP_MSG_STATUS_NOTE_OFF: + midi1->note.note = midi2->note.note; + midi1->note.velocity = downscale_16_to_7bit(midi2->note.velocity); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi1->paf.note = midi2->paf.note; + midi1->paf.data = downscale_32_to_7bit(midi2->paf.data); + break; + case UMP_MSG_STATUS_CC: + midi1->cc.index = midi2->cc.index; + midi1->cc.data = downscale_32_to_7bit(midi2->cc.data); + break; + case UMP_MSG_STATUS_PROGRAM: + midi1->pg.program = midi2->pg.program; + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi1->caf.data = downscale_32_to_7bit(midi2->caf.data); + break; + case UMP_MSG_STATUS_PITCH_BEND: + v = downscale_32_to_14bit(midi2->pb.data); + midi1->pb.data_msb = v >> 7; + midi1->pb.data_lsb = v & 0x7f; + break; + default: + return 0; + } + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); +} + +/* convert UMP to a legacy ALSA seq event and deliver it */ +static int cvt_ump_to_any(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + unsigned char type, + int atomic, int hop) +{ + struct snd_seq_event ev_cvt[2]; /* up to two events */ + struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event; + /* use the second event as a temp buffer for saving stack usage */ + unsigned char *sysex_buf = (unsigned char *)(ev_cvt + 1); + unsigned char flags = event->flags & ~SNDRV_SEQ_EVENT_UMP; + int i, len, err; + + ev_cvt[0] = ev_cvt[1] = *event; + ev_cvt[0].flags = flags; + ev_cvt[1].flags = flags; + switch (type) { + case UMP_MSG_TYPE_SYSTEM: + len = cvt_ump_system_to_event((union snd_ump_midi1_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE: + len = cvt_ump_midi1_to_event((union snd_ump_midi1_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE: + len = cvt_ump_midi2_to_event((union snd_ump_midi2_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_DATA: + len = cvt_ump_sysex7_to_event(ump_ev->ump, sysex_buf, ev_cvt); + break; + default: + return 0; + } + + for (i = 0; i < len; i++) { + err = __snd_seq_deliver_single_event(dest, dest_port, + &ev_cvt[i], atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Replace UMP group field with the destination and deliver */ +static int deliver_with_group_convert(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_ump_event *ump_ev, + int atomic, int hop) +{ + struct snd_seq_ump_event ev = *ump_ev; + + /* rewrite the group to the destination port */ + ev.ump[0] &= ~(0xfU << 24); + /* fill with the new group; the dest_port->ump_group field is 1-based */ + ev.ump[0] |= ((dest_port->ump_group - 1) << 24); + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev, + atomic, hop); +} + +/* apply the UMP event filter; return true to skip the event */ +static bool ump_event_filtered(struct snd_seq_client *dest, + const struct snd_seq_ump_event *ev) +{ + unsigned char group; + + group = ump_message_group(ev->ump[0]); + if (ump_is_groupless_msg(ump_message_type(ev->ump[0]))) + return dest->group_filter & (1U << 0); + /* check the bitmap for 1-based group number */ + return dest->group_filter & (1U << (group + 1)); +} + +/* Convert from UMP packet and deliver */ +int snd_seq_deliver_from_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event; + unsigned char type; + + if (snd_seq_ev_is_variable(event)) + return 0; // skip, no variable event for UMP, so far + if (ump_event_filtered(dest, ump_ev)) + return 0; // skip if group filter is set and matching + type = ump_message_type(ump_ev->ump[0]); + + if (snd_seq_client_is_ump(dest)) { + if (snd_seq_client_is_midi2(dest) && + type == UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE) + return cvt_ump_midi1_to_midi2(dest, dest_port, + event, atomic, hop); + else if (!snd_seq_client_is_midi2(dest) && + type == UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE) + return cvt_ump_midi2_to_midi1(dest, dest_port, + event, atomic, hop); + /* non-EP port and different group is set? */ + if (dest_port->ump_group && + !ump_is_groupless_msg(type) && + ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group) + return deliver_with_group_convert(dest, dest_port, + ump_ev, atomic, hop); + /* copy as-is */ + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + } + + return cvt_ump_to_any(dest, dest_port, event, type, atomic, hop); +} + +/* + * MIDI1 sequencer event -> UMP conversion + */ + +/* Conversion to UMP MIDI 1.0 */ + +/* convert note on/off event to MIDI 1.0 UMP */ +static int note_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + if (!event->data.note.velocity) + status = UMP_MSG_STATUS_NOTE_OFF; + data->note.status = status; + data->note.channel = event->data.note.channel & 0x0f; + data->note.velocity = event->data.note.velocity & 0x7f; + data->note.note = event->data.note.note & 0x7f; + return 1; +} + +/* convert CC event to MIDI 1.0 UMP */ +static int cc_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->cc.status = status; + data->cc.channel = event->data.control.channel & 0x0f; + data->cc.index = event->data.control.param; + data->cc.data = event->data.control.value; + return 1; +} + +/* convert one-parameter control event to MIDI 1.0 UMP */ +static int ctrl_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->caf.status = status; + data->caf.channel = event->data.control.channel & 0x0f; + data->caf.data = event->data.control.value & 0x7f; + return 1; +} + +/* convert pitchbend event to MIDI 1.0 UMP */ +static int pitchbend_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + int val = event->data.control.value + 8192; + + val = clamp(val, 0, 0x3fff); + data->pb.status = status; + data->pb.channel = event->data.control.channel & 0x0f; + data->pb.data_msb = (val >> 7) & 0x7f; + data->pb.data_lsb = val & 0x7f; + return 1; +} + +/* convert 14bit control event to MIDI 1.0 UMP; split to two events */ +static int ctrl14_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = event->data.control.channel & 0x0f; + data->cc.index = event->data.control.param & 0x7f; + if (event->data.control.param < 0x20) { + data->cc.data = (event->data.control.value >> 7) & 0x7f; + data[1] = data[0]; + data[1].cc.index = event->data.control.param | 0x20; + data[1].cc.data = event->data.control.value & 0x7f; + return 2; + } + + data->cc.data = event->data.control.value & 0x7f; + return 1; +} + +/* convert RPN/NRPN event to MIDI 1.0 UMP; split to four events */ +static int rpn_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + bool is_rpn = (status == UMP_MSG_STATUS_RPN); + + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = event->data.control.channel & 0x0f; + data[1] = data[2] = data[3] = data[0]; + + data[0].cc.index = is_rpn ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB; + data[0].cc.data = (event->data.control.param >> 7) & 0x7f; + data[1].cc.index = is_rpn ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB; + data[1].cc.data = event->data.control.param & 0x7f; + data[2].cc.index = UMP_CC_DATA; + data[2].cc.data = (event->data.control.value >> 7) & 0x7f; + data[3].cc.index = UMP_CC_DATA_LSB; + data[3].cc.data = event->data.control.value & 0x7f; + return 4; +} + +/* convert system / RT message to UMP */ +static int system_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + return 1; +} + +/* convert system / RT message with 1 parameter to UMP */ +static int system_1p_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + data->system.parm1 = event->data.control.value & 0x7f; + return 1; +} + +/* convert system / RT message with two parameters to UMP */ +static int system_2p_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + data->system.parm1 = (event->data.control.value >> 7) & 0x7f; + data->system.parm2 = event->data.control.value & 0x7f; + return 1; +} + +/* Conversion to UMP MIDI 2.0 */ + +/* convert note on/off event to MIDI 2.0 UMP */ +static int note_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + if (!event->data.note.velocity) + status = UMP_MSG_STATUS_NOTE_OFF; + data->note.status = status; + data->note.channel = event->data.note.channel & 0x0f; + data->note.note = event->data.note.note & 0x7f; + data->note.velocity = upscale_7_to_16bit(event->data.note.velocity & 0x7f); + return 1; +} + +/* convert PAF event to MIDI 2.0 UMP */ +static int paf_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->paf.status = status; + data->paf.channel = event->data.note.channel & 0x0f; + data->paf.note = event->data.note.note & 0x7f; + data->paf.data = upscale_7_to_32bit(event->data.note.velocity & 0x7f); + return 1; +} + +/* set up the MIDI2 RPN/NRPN packet data from the parsed info */ +static void fill_rpn(struct snd_seq_ump_midi2_bank *cc, + union snd_ump_midi2_msg *data) +{ + if (cc->rpn_set) { + data->rpn.status = UMP_MSG_STATUS_RPN; + data->rpn.bank = cc->cc_rpn_msb; + data->rpn.index = cc->cc_rpn_lsb; + cc->rpn_set = 0; + cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; + } else { + data->rpn.status = UMP_MSG_STATUS_NRPN; + data->rpn.bank = cc->cc_nrpn_msb; + data->rpn.index = cc->cc_nrpn_lsb; + cc->nrpn_set = 0; + cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0; + } + data->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | + cc->cc_data_lsb); + cc->cc_data_msb = cc->cc_data_lsb = 0; +} + +/* convert CC event to MIDI 2.0 UMP */ +static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + unsigned char index = event->data.control.param & 0x7f; + unsigned char val = event->data.control.value & 0x7f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + + /* process special CC's (bank/rpn/nrpn) */ + switch (index) { + case UMP_CC_RPN_MSB: + cc->rpn_set = 1; + cc->cc_rpn_msb = val; + return 0; // skip + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = val; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->nrpn_set = 1; + cc->cc_nrpn_msb = val; + return 0; // skip + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = val; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = val; + return 0; // skip + case UMP_CC_BANK_SELECT: + cc->bank_set = 1; + cc->cc_bank_msb = val; + return 0; // skip + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = val; + return 0; // skip + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = val; + if (!(cc->rpn_set || cc->nrpn_set)) + return 0; // skip + fill_rpn(cc, data); + return 1; + } + + data->cc.status = status; + data->cc.channel = channel; + data->cc.index = index; + data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + return 1; +} + +/* convert one-parameter control event to MIDI 2.0 UMP */ +static int ctrl_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->caf.status = status; + data->caf.channel = event->data.control.channel & 0x0f; + data->caf.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + return 1; +} + +/* convert program change event to MIDI 2.0 UMP */ +static int pgm_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + + data->pg.status = status; + data->pg.channel = channel; + data->pg.program = event->data.control.value & 0x7f; + if (cc->bank_set) { + data->pg.bank_valid = 1; + data->pg.bank_msb = cc->cc_bank_msb; + data->pg.bank_lsb = cc->cc_bank_lsb; + cc->bank_set = 0; + cc->cc_bank_msb = cc->cc_bank_lsb = 0; + } + return 1; +} + +/* convert pitchbend event to MIDI 2.0 UMP */ +static int pitchbend_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + int val = event->data.control.value + 8192; + + val = clamp(val, 0, 0x3fff); + data->pb.status = status; + data->pb.channel = event->data.control.channel & 0x0f; + data->pb.data = upscale_14_to_32bit(val); + return 1; +} + +/* convert 14bit control event to MIDI 2.0 UMP; split to two events */ +static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + unsigned char index = event->data.control.param & 0x7f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + unsigned char msb, lsb; + + msb = (event->data.control.value >> 7) & 0x7f; + lsb = event->data.control.value & 0x7f; + /* process special CC's (bank/rpn/nrpn) */ + switch (index) { + case UMP_CC_BANK_SELECT: + cc->cc_bank_msb = msb; + fallthrough; + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = lsb; + return 0; // skip + case UMP_CC_RPN_MSB: + cc->cc_rpn_msb = msb; + fallthrough; + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = lsb; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->cc_nrpn_msb = msb; + fallthrough; + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = lsb; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = msb; + fallthrough; + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = lsb; + if (!(cc->rpn_set || cc->nrpn_set)) + return 0; // skip + fill_rpn(cc, data); + return 1; + } + + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = channel; + data->cc.index = index; + if (event->data.control.param < 0x20) { + data->cc.data = upscale_7_to_32bit(msb); + data[1] = data[0]; + data[1].cc.index = event->data.control.param | 0x20; + data[1].cc.data = upscale_7_to_32bit(lsb); + return 2; + } + + data->cc.data = upscale_7_to_32bit(lsb); + return 1; +} + +/* convert RPN/NRPN event to MIDI 2.0 UMP */ +static int rpn_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->rpn.status = status; + data->rpn.channel = event->data.control.channel; + data->rpn.bank = (event->data.control.param >> 7) & 0x7f; + data->rpn.index = event->data.control.param & 0x7f; + data->rpn.data = upscale_14_to_32bit(event->data.control.value & 0x3fff); + return 1; +} + +/* convert system / RT message to UMP */ +static int system_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +/* convert system / RT message with 1 parameter to UMP */ +static int system_1p_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_1p_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +/* convert system / RT message with two parameters to UMP */ +static int system_2p_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_1p_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +struct seq_ev_to_ump { + int seq_type; + unsigned char status; + int (*midi1_encode)(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status); + int (*midi2_encode)(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status); +}; + +static const struct seq_ev_to_ump seq_ev_ump_encoders[] = { + { SNDRV_SEQ_EVENT_NOTEON, UMP_MSG_STATUS_NOTE_ON, + note_ev_to_ump_midi1, note_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_NOTEOFF, UMP_MSG_STATUS_NOTE_OFF, + note_ev_to_ump_midi1, note_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_KEYPRESS, UMP_MSG_STATUS_POLY_PRESSURE, + note_ev_to_ump_midi1, paf_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTROLLER, UMP_MSG_STATUS_CC, + cc_ev_to_ump_midi1, cc_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_PGMCHANGE, UMP_MSG_STATUS_PROGRAM, + ctrl_ev_to_ump_midi1, pgm_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CHANPRESS, UMP_MSG_STATUS_CHANNEL_PRESSURE, + ctrl_ev_to_ump_midi1, ctrl_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_PITCHBEND, UMP_MSG_STATUS_PITCH_BEND, + pitchbend_ev_to_ump_midi1, pitchbend_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTROL14, 0, + ctrl14_ev_to_ump_midi1, ctrl14_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_NONREGPARAM, UMP_MSG_STATUS_NRPN, + rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_REGPARAM, UMP_MSG_STATUS_RPN, + rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_QFRAME, UMP_SYSTEM_STATUS_MIDI_TIME_CODE, + system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SONGPOS, UMP_SYSTEM_STATUS_SONG_POSITION, + system_2p_ev_to_ump_midi1, system_2p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SONGSEL, UMP_SYSTEM_STATUS_SONG_SELECT, + system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_TUNE_REQUEST, UMP_SYSTEM_STATUS_TUNE_REQUEST, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CLOCK, UMP_SYSTEM_STATUS_TIMING_CLOCK, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_START, UMP_SYSTEM_STATUS_START, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTINUE, UMP_SYSTEM_STATUS_CONTINUE, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_STOP, UMP_SYSTEM_STATUS_STOP, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SENSING, UMP_SYSTEM_STATUS_ACTIVE_SENSING, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, +}; + +static const struct seq_ev_to_ump *find_ump_encoder(int type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seq_ev_ump_encoders); i++) + if (seq_ev_ump_encoders[i].seq_type == type) + return &seq_ev_ump_encoders[i]; + + return NULL; +} + +static void setup_ump_event(struct snd_seq_ump_event *dest, + const struct snd_seq_event *src) +{ + memcpy(dest, src, sizeof(*src)); + dest->type = 0; + dest->flags |= SNDRV_SEQ_EVENT_UMP; + dest->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + memset(dest->ump, 0, sizeof(dest->ump)); +} + +/* Convert ALSA seq event to UMP MIDI 1.0 and deliver it */ +static int cvt_to_ump_midi1(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + const struct seq_ev_to_ump *encoder; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi1_msg data[4]; + int i, n, err; + + encoder = find_ump_encoder(event->type); + if (!encoder) + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + + data->raw = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE); + n = encoder->midi1_encode(event, dest_port, data, encoder->status); + if (!n) + return 0; + + setup_ump_event(&ev_cvt, event); + for (i = 0; i < n; i++) { + ev_cvt.ump[0] = data[i].raw; + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Convert ALSA seq event to UMP MIDI 2.0 and deliver it */ +static int cvt_to_ump_midi2(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + const struct seq_ev_to_ump *encoder; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi2_msg data[2]; + int i, n, err; + + encoder = find_ump_encoder(event->type); + if (!encoder) + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + + data->raw[0] = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE); + data->raw[1] = 0; + n = encoder->midi2_encode(event, dest_port, data, encoder->status); + if (!n) + return 0; + + setup_ump_event(&ev_cvt, event); + for (i = 0; i < n; i++) { + memcpy(ev_cvt.ump, &data[i], sizeof(data[i])); + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Fill up a sysex7 UMP from the byte stream */ +static void fill_sysex7_ump(struct snd_seq_client_port *dest_port, + u32 *val, u8 status, u8 *buf, int len) +{ + memset(val, 0, 8); + memcpy((u8 *)val + 2, buf, len); +#ifdef __LITTLE_ENDIAN + swab32_array(val, 2); +#endif + val[0] |= ump_compose(UMP_MSG_TYPE_DATA, get_ump_group(dest_port), + status, len); +} + +/* Convert sysex var event to UMP sysex7 packets and deliver them */ +static int cvt_sysex_to_ump(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_ump_event ev_cvt; + unsigned char status; + u8 buf[6], *xbuf; + int offset = 0; + int len, err; + + if (!snd_seq_ev_is_variable(event)) + return 0; + + setup_ump_event(&ev_cvt, event); + for (;;) { + len = snd_seq_expand_var_event_at(event, sizeof(buf), buf, offset); + if (len <= 0) + break; + if (WARN_ON(len > 6)) + break; + offset += len; + xbuf = buf; + if (*xbuf == UMP_MIDI1_MSG_SYSEX_START) { + status = UMP_SYSEX_STATUS_START; + xbuf++; + len--; + if (len > 0 && xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) { + status = UMP_SYSEX_STATUS_SINGLE; + len--; + } + } else { + if (xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) { + status = UMP_SYSEX_STATUS_END; + len--; + } else { + status = UMP_SYSEX_STATUS_CONTINUE; + } + } + fill_sysex7_ump(dest_port, ev_cvt.ump, status, xbuf, len); + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + return 0; +} + +/* Convert to UMP packet and deliver */ +int snd_seq_deliver_to_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + if (dest->group_filter & (1U << dest_port->ump_group)) + return 0; /* group filtered - skip the event */ + if (event->type == SNDRV_SEQ_EVENT_SYSEX) + return cvt_sysex_to_ump(dest, dest_port, event, atomic, hop); + else if (snd_seq_client_is_midi2(dest)) + return cvt_to_ump_midi2(dest, dest_port, event, atomic, hop); + else + return cvt_to_ump_midi1(dest, dest_port, event, atomic, hop); +} diff --git a/sound/core/seq/seq_ump_convert.h b/sound/core/seq/seq_ump_convert.h new file mode 100644 index 0000000000..6c146d8032 --- /dev/null +++ b/sound/core/seq/seq_ump_convert.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer event conversion between UMP and legacy clients + */ +#ifndef __SEQ_UMP_CONVERT_H +#define __SEQ_UMP_CONVERT_H + +#include "seq_clientmgr.h" +#include "seq_ports.h" + +int snd_seq_deliver_from_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); +int snd_seq_deliver_to_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); + +#endif /* __SEQ_UMP_CONVERT_H */ diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c new file mode 100644 index 0000000000..1b9260108e --- /dev/null +++ b/sound/core/seq/seq_virmidi.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Virtual Raw MIDI client on Sequencer + * + * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>, + * Jaroslav Kysela <perex@perex.cz> + */ + +/* + * Virtual Raw MIDI client + * + * The virtual rawmidi client is a sequencer client which associate + * a rawmidi device file. The created rawmidi device file can be + * accessed as a normal raw midi, but its MIDI source and destination + * are arbitrary. For example, a user-client software synth connected + * to this port can be used as a normal midi device as well. + * + * The virtual rawmidi device accepts also multiple opens. Each file + * has its own input buffer, so that no conflict would occur. The drain + * of input/output buffer acts only to the local buffer. + * + */ + +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/info.h> +#include <sound/control.h> +#include <sound/minors.h> +#include <sound/seq_kernel.h> +#include <sound/seq_midi_event.h> +#include <sound/seq_virmidi.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("Virtual Raw MIDI client on Sequencer"); +MODULE_LICENSE("GPL"); + +/* + * initialize an event record + */ +static void snd_virmidi_init_event(struct snd_virmidi *vmidi, + struct snd_seq_event *ev) +{ + memset(ev, 0, sizeof(*ev)); + ev->source.port = vmidi->port; + switch (vmidi->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + ev->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + /* FIXME: source and destination are same - not good.. */ + ev->dest.client = vmidi->client; + ev->dest.port = vmidi->port; + break; + } + ev->type = SNDRV_SEQ_EVENT_NONE; +} + +/* + * decode input event and put to read buffer of each opened file + */ +static int snd_virmidi_dev_receive_event(struct snd_virmidi_dev *rdev, + struct snd_seq_event *ev, + bool atomic) +{ + struct snd_virmidi *vmidi; + unsigned char msg[4]; + int len; + + if (atomic) + read_lock(&rdev->filelist_lock); + else + down_read(&rdev->filelist_sem); + list_for_each_entry(vmidi, &rdev->filelist, list) { + if (!READ_ONCE(vmidi->trigger)) + continue; + if (ev->type == SNDRV_SEQ_EVENT_SYSEX) { + if ((ev->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) != SNDRV_SEQ_EVENT_LENGTH_VARIABLE) + continue; + snd_seq_dump_var_event(ev, (snd_seq_dump_func_t)snd_rawmidi_receive, vmidi->substream); + snd_midi_event_reset_decode(vmidi->parser); + } else { + len = snd_midi_event_decode(vmidi->parser, msg, sizeof(msg), ev); + if (len > 0) + snd_rawmidi_receive(vmidi->substream, msg, len); + } + } + if (atomic) + read_unlock(&rdev->filelist_lock); + else + up_read(&rdev->filelist_sem); + + return 0; +} + +/* + * event handler of virmidi port + */ +static int snd_virmidi_event_input(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!(rdev->flags & SNDRV_VIRMIDI_USE)) + return 0; /* ignored */ + return snd_virmidi_dev_receive_event(rdev, ev, atomic); +} + +/* + * trigger rawmidi stream for input + */ +static void snd_virmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, !!up); +} + +/* process rawmidi bytes and send events; + * we need no lock here for vmidi->event since it's handled only in this work + */ +static void snd_vmidi_output_work(struct work_struct *work) +{ + struct snd_virmidi *vmidi; + struct snd_rawmidi_substream *substream; + unsigned char input; + int ret; + + vmidi = container_of(work, struct snd_virmidi, output_work); + substream = vmidi->substream; + + /* discard the outputs in dispatch mode unless subscribed */ + if (vmidi->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH && + !(vmidi->rdev->flags & SNDRV_VIRMIDI_SUBSCRIBE)) { + snd_rawmidi_proceed(substream); + return; + } + + while (READ_ONCE(vmidi->trigger)) { + if (snd_rawmidi_transmit(substream, &input, 1) != 1) + break; + if (!snd_midi_event_encode_byte(vmidi->parser, input, + &vmidi->event)) + continue; + if (vmidi->event.type != SNDRV_SEQ_EVENT_NONE) { + ret = snd_seq_kernel_client_dispatch(vmidi->client, + &vmidi->event, + false, 0); + vmidi->event.type = SNDRV_SEQ_EVENT_NONE; + if (ret < 0) + break; + } + /* rawmidi input might be huge, allow to have a break */ + cond_resched(); + } +} + +/* + * trigger rawmidi stream for output + */ +static void snd_virmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, !!up); + if (up) + queue_work(system_highpri_wq, &vmidi->output_work); +} + +/* + * open rawmidi handle for input + */ +static int snd_virmidi_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_virmidi *vmidi; + + vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(0, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + runtime->private_data = vmidi; + down_write(&rdev->filelist_sem); + write_lock_irq(&rdev->filelist_lock); + list_add_tail(&vmidi->list, &rdev->filelist); + write_unlock_irq(&rdev->filelist_lock); + up_write(&rdev->filelist_sem); + vmidi->rdev = rdev; + return 0; +} + +/* + * open rawmidi handle for output + */ +static int snd_virmidi_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_virmidi *vmidi; + + vmidi = kzalloc(sizeof(*vmidi), GFP_KERNEL); + if (vmidi == NULL) + return -ENOMEM; + vmidi->substream = substream; + if (snd_midi_event_new(MAX_MIDI_EVENT_BUF, &vmidi->parser) < 0) { + kfree(vmidi); + return -ENOMEM; + } + vmidi->seq_mode = rdev->seq_mode; + vmidi->client = rdev->client; + vmidi->port = rdev->port; + snd_virmidi_init_event(vmidi, &vmidi->event); + vmidi->rdev = rdev; + INIT_WORK(&vmidi->output_work, snd_vmidi_output_work); + runtime->private_data = vmidi; + return 0; +} + +/* + * close rawmidi handle for input + */ +static int snd_virmidi_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi_dev *rdev = substream->rmidi->private_data; + struct snd_virmidi *vmidi = substream->runtime->private_data; + + down_write(&rdev->filelist_sem); + write_lock_irq(&rdev->filelist_lock); + list_del(&vmidi->list); + write_unlock_irq(&rdev->filelist_lock); + up_write(&rdev->filelist_sem); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * close rawmidi handle for output + */ +static int snd_virmidi_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + WRITE_ONCE(vmidi->trigger, false); /* to be sure */ + cancel_work_sync(&vmidi->output_work); + snd_midi_event_free(vmidi->parser); + substream->runtime->private_data = NULL; + kfree(vmidi); + return 0; +} + +/* + * drain output work queue + */ +static void snd_virmidi_output_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_virmidi *vmidi = substream->runtime->private_data; + + flush_work(&vmidi->output_work); +} + +/* + * subscribe callback - allow output to rawmidi device + */ +static int snd_virmidi_subscribe(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_SUBSCRIBE; + return 0; +} + +/* + * unsubscribe callback - disallow output to rawmidi device + */ +static int snd_virmidi_unsubscribe(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_SUBSCRIBE; + module_put(rdev->card->module); + return 0; +} + + +/* + * use callback - allow input to rawmidi device + */ +static int snd_virmidi_use(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + if (!try_module_get(rdev->card->module)) + return -EFAULT; + rdev->flags |= SNDRV_VIRMIDI_USE; + return 0; +} + +/* + * unuse callback - disallow input to rawmidi device + */ +static int snd_virmidi_unuse(void *private_data, + struct snd_seq_port_subscribe *info) +{ + struct snd_virmidi_dev *rdev; + + rdev = private_data; + rdev->flags &= ~SNDRV_VIRMIDI_USE; + module_put(rdev->card->module); + return 0; +} + + +/* + * Register functions + */ + +static const struct snd_rawmidi_ops snd_virmidi_input_ops = { + .open = snd_virmidi_input_open, + .close = snd_virmidi_input_close, + .trigger = snd_virmidi_input_trigger, +}; + +static const struct snd_rawmidi_ops snd_virmidi_output_ops = { + .open = snd_virmidi_output_open, + .close = snd_virmidi_output_close, + .trigger = snd_virmidi_output_trigger, + .drain = snd_virmidi_output_drain, +}; + +/* + * create a sequencer client and a port + */ +static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev) +{ + int client; + struct snd_seq_port_callback pcallbacks; + struct snd_seq_port_info *pinfo; + int err; + + if (rdev->client >= 0) + return 0; + + pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); + if (!pinfo) { + err = -ENOMEM; + goto __error; + } + + client = snd_seq_create_kernel_client(rdev->card, rdev->device, + "%s %d-%d", rdev->rmidi->name, + rdev->card->number, + rdev->device); + if (client < 0) { + err = client; + goto __error; + } + rdev->client = client; + + /* create a port */ + pinfo->addr.client = client; + sprintf(pinfo->name, "VirMIDI %d-%d", rdev->card->number, rdev->device); + /* set all capabilities */ + pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; + pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo->direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION; + pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC + | SNDRV_SEQ_PORT_TYPE_SOFTWARE + | SNDRV_SEQ_PORT_TYPE_PORT; + pinfo->midi_channels = 16; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = rdev; + pcallbacks.subscribe = snd_virmidi_subscribe; + pcallbacks.unsubscribe = snd_virmidi_unsubscribe; + pcallbacks.use = snd_virmidi_use; + pcallbacks.unuse = snd_virmidi_unuse; + pcallbacks.event_input = snd_virmidi_event_input; + pinfo->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_CREATE_PORT, pinfo); + if (err < 0) { + snd_seq_delete_kernel_client(client); + rdev->client = -1; + goto __error; + } + + rdev->port = pinfo->addr.port; + err = 0; /* success */ + + __error: + kfree(pinfo); + return err; +} + + +/* + * release the sequencer client + */ +static void snd_virmidi_dev_detach_seq(struct snd_virmidi_dev *rdev) +{ + if (rdev->client >= 0) { + snd_seq_delete_kernel_client(rdev->client); + rdev->client = -1; + } +} + +/* + * register the device + */ +static int snd_virmidi_dev_register(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + int err; + + switch (rdev->seq_mode) { + case SNDRV_VIRMIDI_SEQ_DISPATCH: + err = snd_virmidi_dev_attach_seq(rdev); + if (err < 0) + return err; + break; + case SNDRV_VIRMIDI_SEQ_ATTACH: + if (rdev->client == 0) + return -EINVAL; + /* should check presence of port more strictly.. */ + break; + default: + pr_err("ALSA: seq_virmidi: seq_mode is not set: %d\n", rdev->seq_mode); + return -EINVAL; + } + return 0; +} + + +/* + * unregister the device + */ +static int snd_virmidi_dev_unregister(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + + if (rdev->seq_mode == SNDRV_VIRMIDI_SEQ_DISPATCH) + snd_virmidi_dev_detach_seq(rdev); + return 0; +} + +/* + * + */ +static const struct snd_rawmidi_global_ops snd_virmidi_global_ops = { + .dev_register = snd_virmidi_dev_register, + .dev_unregister = snd_virmidi_dev_unregister, +}; + +/* + * free device + */ +static void snd_virmidi_free(struct snd_rawmidi *rmidi) +{ + struct snd_virmidi_dev *rdev = rmidi->private_data; + kfree(rdev); +} + +/* + * create a new device + * + */ +/* exported */ +int snd_virmidi_new(struct snd_card *card, int device, struct snd_rawmidi **rrmidi) +{ + struct snd_rawmidi *rmidi; + struct snd_virmidi_dev *rdev; + int err; + + *rrmidi = NULL; + err = snd_rawmidi_new(card, "VirMidi", device, + 16, /* may be configurable */ + 16, /* may be configurable */ + &rmidi); + if (err < 0) + return err; + strcpy(rmidi->name, rmidi->id); + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); + if (rdev == NULL) { + snd_device_free(card, rmidi); + return -ENOMEM; + } + rdev->card = card; + rdev->rmidi = rmidi; + rdev->device = device; + rdev->client = -1; + init_rwsem(&rdev->filelist_sem); + rwlock_init(&rdev->filelist_lock); + INIT_LIST_HEAD(&rdev->filelist); + rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH; + rmidi->private_data = rdev; + rmidi->private_free = snd_virmidi_free; + rmidi->ops = &snd_virmidi_global_ops; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_virmidi_input_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_virmidi_output_ops); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + *rrmidi = rmidi; + return 0; +} +EXPORT_SYMBOL(snd_virmidi_new); diff --git a/sound/core/seq_device.c b/sound/core/seq_device.c new file mode 100644 index 0000000000..7f3fd8eb01 --- /dev/null +++ b/sound/core/seq_device.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer device management + * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> + * + *---------------------------------------------------------------- + * + * This device handler separates the card driver module from sequencer + * stuff (sequencer core, synth drivers, etc), so that user can avoid + * to spend unnecessary resources e.g. if he needs only listening to + * MP3s. + * + * The card (or lowlevel) driver creates a sequencer device entry + * via snd_seq_device_new(). This is an entry pointer to communicate + * with the sequencer device "driver", which is involved with the + * actual part to communicate with the sequencer core. + * Each sequencer device entry has an id string and the corresponding + * driver with the same id is loaded when required. For example, + * lowlevel codes to access emu8000 chip on sbawe card are included in + * emu8000-synth module. To activate this module, the hardware + * resources like i/o port are passed via snd_seq_device argument. + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/seq_device.h> +#include <sound/seq_kernel.h> +#include <sound/initval.h> +#include <linux/kmod.h> +#include <linux/slab.h> +#include <linux/mutex.h> + +MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA sequencer device management"); +MODULE_LICENSE("GPL"); + +/* + * bus definition + */ +static int snd_seq_bus_match(struct device *dev, struct device_driver *drv) +{ + struct snd_seq_device *sdev = to_seq_dev(dev); + struct snd_seq_driver *sdrv = to_seq_drv(drv); + + return strcmp(sdrv->id, sdev->id) == 0 && + sdrv->argsize == sdev->argsize; +} + +static struct bus_type snd_seq_bus_type = { + .name = "snd_seq", + .match = snd_seq_bus_match, +}; + +/* + * proc interface -- just for compatibility + */ +#ifdef CONFIG_SND_PROC_FS +static struct snd_info_entry *info_entry; + +static int print_dev_info(struct device *dev, void *data) +{ + struct snd_seq_device *sdev = to_seq_dev(dev); + struct snd_info_buffer *buffer = data; + + snd_iprintf(buffer, "snd-%s,%s,%d\n", sdev->id, + dev->driver ? "loaded" : "empty", + dev->driver ? 1 : 0); + return 0; +} + +static void snd_seq_device_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + bus_for_each_dev(&snd_seq_bus_type, NULL, buffer, print_dev_info); +} +#endif + +/* + * load all registered drivers (called from seq_clientmgr.c) + */ + +#ifdef CONFIG_MODULES +/* flag to block auto-loading */ +static atomic_t snd_seq_in_init = ATOMIC_INIT(1); /* blocked as default */ + +static int request_seq_drv(struct device *dev, void *data) +{ + struct snd_seq_device *sdev = to_seq_dev(dev); + + if (!dev->driver) + request_module("snd-%s", sdev->id); + return 0; +} + +static void autoload_drivers(struct work_struct *work) +{ + /* avoid reentrance */ + if (atomic_inc_return(&snd_seq_in_init) == 1) + bus_for_each_dev(&snd_seq_bus_type, NULL, NULL, + request_seq_drv); + atomic_dec(&snd_seq_in_init); +} + +static DECLARE_WORK(autoload_work, autoload_drivers); + +static void queue_autoload_drivers(void) +{ + schedule_work(&autoload_work); +} + +void snd_seq_autoload_init(void) +{ + atomic_dec(&snd_seq_in_init); +#ifdef CONFIG_SND_SEQUENCER_MODULE + /* initial autoload only when snd-seq is a module */ + queue_autoload_drivers(); +#endif +} +EXPORT_SYMBOL(snd_seq_autoload_init); + +void snd_seq_autoload_exit(void) +{ + atomic_inc(&snd_seq_in_init); +} +EXPORT_SYMBOL(snd_seq_autoload_exit); + +void snd_seq_device_load_drivers(void) +{ + queue_autoload_drivers(); + flush_work(&autoload_work); +} +EXPORT_SYMBOL(snd_seq_device_load_drivers); + +static inline void cancel_autoload_drivers(void) +{ + cancel_work_sync(&autoload_work); +} +#else +static inline void queue_autoload_drivers(void) +{ +} + +static inline void cancel_autoload_drivers(void) +{ +} +#endif + +/* + * device management + */ +static int snd_seq_device_dev_free(struct snd_device *device) +{ + struct snd_seq_device *dev = device->device_data; + + cancel_autoload_drivers(); + if (dev->private_free) + dev->private_free(dev); + put_device(&dev->dev); + return 0; +} + +static int snd_seq_device_dev_register(struct snd_device *device) +{ + struct snd_seq_device *dev = device->device_data; + int err; + + err = device_add(&dev->dev); + if (err < 0) + return err; + if (!dev->dev.driver) + queue_autoload_drivers(); + return 0; +} + +static int snd_seq_device_dev_disconnect(struct snd_device *device) +{ + struct snd_seq_device *dev = device->device_data; + + device_del(&dev->dev); + return 0; +} + +static void snd_seq_dev_release(struct device *dev) +{ + kfree(to_seq_dev(dev)); +} + +/* + * register a sequencer device + * card = card info + * device = device number (if any) + * id = id of driver + * result = return pointer (NULL allowed if unnecessary) + */ +int snd_seq_device_new(struct snd_card *card, int device, const char *id, + int argsize, struct snd_seq_device **result) +{ + struct snd_seq_device *dev; + int err; + static const struct snd_device_ops dops = { + .dev_free = snd_seq_device_dev_free, + .dev_register = snd_seq_device_dev_register, + .dev_disconnect = snd_seq_device_dev_disconnect, + }; + + if (result) + *result = NULL; + + if (snd_BUG_ON(!id)) + return -EINVAL; + + dev = kzalloc(sizeof(*dev) + argsize, GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* set up device info */ + dev->card = card; + dev->device = device; + dev->id = id; + dev->argsize = argsize; + + device_initialize(&dev->dev); + dev->dev.parent = &card->card_dev; + dev->dev.bus = &snd_seq_bus_type; + dev->dev.release = snd_seq_dev_release; + dev_set_name(&dev->dev, "%s-%d-%d", dev->id, card->number, device); + + /* add this device to the list */ + err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops); + if (err < 0) { + put_device(&dev->dev); + return err; + } + + if (result) + *result = dev; + + return 0; +} +EXPORT_SYMBOL(snd_seq_device_new); + +/* + * driver registration + */ +int __snd_seq_driver_register(struct snd_seq_driver *drv, struct module *mod) +{ + if (WARN_ON(!drv->driver.name || !drv->id)) + return -EINVAL; + drv->driver.bus = &snd_seq_bus_type; + drv->driver.owner = mod; + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__snd_seq_driver_register); + +void snd_seq_driver_unregister(struct snd_seq_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(snd_seq_driver_unregister); + +/* + * module part + */ + +static int __init seq_dev_proc_init(void) +{ +#ifdef CONFIG_SND_PROC_FS + info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", + snd_seq_root); + if (info_entry == NULL) + return -ENOMEM; + info_entry->content = SNDRV_INFO_CONTENT_TEXT; + info_entry->c.text.read = snd_seq_device_info; + if (snd_info_register(info_entry) < 0) { + snd_info_free_entry(info_entry); + return -ENOMEM; + } +#endif + return 0; +} + +static int __init alsa_seq_device_init(void) +{ + int err; + + err = bus_register(&snd_seq_bus_type); + if (err < 0) + return err; + err = seq_dev_proc_init(); + if (err < 0) + bus_unregister(&snd_seq_bus_type); + return err; +} + +static void __exit alsa_seq_device_exit(void) +{ +#ifdef CONFIG_MODULES + cancel_work_sync(&autoload_work); +#endif +#ifdef CONFIG_SND_PROC_FS + snd_info_free_entry(info_entry); +#endif + bus_unregister(&snd_seq_bus_type); +} + +subsys_initcall(alsa_seq_device_init) +module_exit(alsa_seq_device_exit) diff --git a/sound/core/sound.c b/sound/core/sound.c new file mode 100644 index 0000000000..df5571d986 --- /dev/null +++ b/sound/core/sound.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Advanced Linux Sound Architecture + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <sound/control.h> +#include <sound/initval.h> +#include <linux/kmod.h> +#include <linux/mutex.h> + +static int major = CONFIG_SND_MAJOR; +int snd_major; +EXPORT_SYMBOL(snd_major); + +static int cards_limit = 1; + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Advanced Linux Sound Architecture driver for soundcards."); +MODULE_LICENSE("GPL"); +module_param(major, int, 0444); +MODULE_PARM_DESC(major, "Major # for sound driver."); +module_param(cards_limit, int, 0444); +MODULE_PARM_DESC(cards_limit, "Count of auto-loadable soundcards."); +MODULE_ALIAS_CHARDEV_MAJOR(CONFIG_SND_MAJOR); + +/* this one holds the actual max. card number currently available. + * as default, it's identical with cards_limit option. when more + * modules are loaded manually, this limit number increases, too. + */ +int snd_ecards_limit; +EXPORT_SYMBOL(snd_ecards_limit); + +#ifdef CONFIG_SND_DEBUG +struct dentry *sound_debugfs_root; +EXPORT_SYMBOL_GPL(sound_debugfs_root); +#endif + +static struct snd_minor *snd_minors[SNDRV_OS_MINORS]; +static DEFINE_MUTEX(sound_mutex); + +#ifdef CONFIG_MODULES + +/** + * snd_request_card - try to load the card module + * @card: the card number + * + * Tries to load the module "snd-card-X" for the given card number + * via request_module. Returns immediately if already loaded. + */ +void snd_request_card(int card) +{ + if (snd_card_locked(card)) + return; + if (card < 0 || card >= cards_limit) + return; + request_module("snd-card-%i", card); +} +EXPORT_SYMBOL(snd_request_card); + +static void snd_request_other(int minor) +{ + char *str; + + switch (minor) { + case SNDRV_MINOR_SEQUENCER: str = "snd-seq"; break; + case SNDRV_MINOR_TIMER: str = "snd-timer"; break; + default: return; + } + request_module(str); +} + +#endif /* modular kernel */ + +/** + * snd_lookup_minor_data - get user data of a registered device + * @minor: the minor number + * @type: device type (SNDRV_DEVICE_TYPE_XXX) + * + * Checks that a minor device with the specified type is registered, and returns + * its user data pointer. + * + * This function increments the reference counter of the card instance + * if an associated instance with the given minor number and type is found. + * The caller must call snd_card_unref() appropriately later. + * + * Return: The user data pointer if the specified device is found. %NULL + * otherwise. + */ +void *snd_lookup_minor_data(unsigned int minor, int type) +{ + struct snd_minor *mreg; + void *private_data; + + if (minor >= ARRAY_SIZE(snd_minors)) + return NULL; + mutex_lock(&sound_mutex); + mreg = snd_minors[minor]; + if (mreg && mreg->type == type) { + private_data = mreg->private_data; + if (private_data && mreg->card_ptr) + get_device(&mreg->card_ptr->card_dev); + } else + private_data = NULL; + mutex_unlock(&sound_mutex); + return private_data; +} +EXPORT_SYMBOL(snd_lookup_minor_data); + +#ifdef CONFIG_MODULES +static struct snd_minor *autoload_device(unsigned int minor) +{ + int dev; + mutex_unlock(&sound_mutex); /* release lock temporarily */ + dev = SNDRV_MINOR_DEVICE(minor); + if (dev == SNDRV_MINOR_CONTROL) { + /* /dev/aloadC? */ + int card = SNDRV_MINOR_CARD(minor); + struct snd_card *ref = snd_card_ref(card); + if (!ref) + snd_request_card(card); + else + snd_card_unref(ref); + } else if (dev == SNDRV_MINOR_GLOBAL) { + /* /dev/aloadSEQ */ + snd_request_other(minor); + } + mutex_lock(&sound_mutex); /* reacuire lock */ + return snd_minors[minor]; +} +#else /* !CONFIG_MODULES */ +#define autoload_device(minor) NULL +#endif /* CONFIG_MODULES */ + +static int snd_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct snd_minor *mptr = NULL; + const struct file_operations *new_fops; + int err = 0; + + if (minor >= ARRAY_SIZE(snd_minors)) + return -ENODEV; + mutex_lock(&sound_mutex); + mptr = snd_minors[minor]; + if (mptr == NULL) { + mptr = autoload_device(minor); + if (!mptr) { + mutex_unlock(&sound_mutex); + return -ENODEV; + } + } + new_fops = fops_get(mptr->f_ops); + mutex_unlock(&sound_mutex); + if (!new_fops) + return -ENODEV; + replace_fops(file, new_fops); + + if (file->f_op->open) + err = file->f_op->open(inode, file); + return err; +} + +static const struct file_operations snd_fops = +{ + .owner = THIS_MODULE, + .open = snd_open, + .llseek = noop_llseek, +}; + +#ifdef CONFIG_SND_DYNAMIC_MINORS +static int snd_find_free_minor(int type, struct snd_card *card, int dev) +{ + int minor; + + /* static minors for module auto loading */ + if (type == SNDRV_DEVICE_TYPE_SEQUENCER) + return SNDRV_MINOR_SEQUENCER; + if (type == SNDRV_DEVICE_TYPE_TIMER) + return SNDRV_MINOR_TIMER; + + for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + /* skip static minors still used for module auto loading */ + if (SNDRV_MINOR_DEVICE(minor) == SNDRV_MINOR_CONTROL) + continue; + if (minor == SNDRV_MINOR_SEQUENCER || + minor == SNDRV_MINOR_TIMER) + continue; + if (!snd_minors[minor]) + return minor; + } + return -EBUSY; +} +#else +static int snd_find_free_minor(int type, struct snd_card *card, int dev) +{ + int minor; + + switch (type) { + case SNDRV_DEVICE_TYPE_SEQUENCER: + case SNDRV_DEVICE_TYPE_TIMER: + minor = type; + break; + case SNDRV_DEVICE_TYPE_CONTROL: + if (snd_BUG_ON(!card)) + return -EINVAL; + minor = SNDRV_MINOR(card->number, type); + break; + case SNDRV_DEVICE_TYPE_HWDEP: + case SNDRV_DEVICE_TYPE_RAWMIDI: + case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: + case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + case SNDRV_DEVICE_TYPE_COMPRESS: + if (snd_BUG_ON(!card)) + return -EINVAL; + minor = SNDRV_MINOR(card->number, type + dev); + break; + default: + return -EINVAL; + } + if (snd_BUG_ON(minor < 0 || minor >= SNDRV_OS_MINORS)) + return -EINVAL; + if (snd_minors[minor]) + return -EBUSY; + return minor; +} +#endif + +/** + * snd_register_device - Register the ALSA device file for the card + * @type: the device type, SNDRV_DEVICE_TYPE_XXX + * @card: the card instance + * @dev: the device index + * @f_ops: the file operations + * @private_data: user pointer for f_ops->open() + * @device: the device to register + * + * Registers an ALSA device file for the given card. + * The operators have to be set in reg parameter. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_register_device(int type, struct snd_card *card, int dev, + const struct file_operations *f_ops, + void *private_data, struct device *device) +{ + int minor; + int err = 0; + struct snd_minor *preg; + + if (snd_BUG_ON(!device)) + return -EINVAL; + + preg = kmalloc(sizeof *preg, GFP_KERNEL); + if (preg == NULL) + return -ENOMEM; + preg->type = type; + preg->card = card ? card->number : -1; + preg->device = dev; + preg->f_ops = f_ops; + preg->private_data = private_data; + preg->card_ptr = card; + mutex_lock(&sound_mutex); + minor = snd_find_free_minor(type, card, dev); + if (minor < 0) { + err = minor; + goto error; + } + + preg->dev = device; + device->devt = MKDEV(major, minor); + err = device_add(device); + if (err < 0) + goto error; + + snd_minors[minor] = preg; + error: + mutex_unlock(&sound_mutex); + if (err < 0) + kfree(preg); + return err; +} +EXPORT_SYMBOL(snd_register_device); + +/** + * snd_unregister_device - unregister the device on the given card + * @dev: the device instance + * + * Unregisters the device file already registered via + * snd_register_device(). + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_unregister_device(struct device *dev) +{ + int minor; + struct snd_minor *preg; + + mutex_lock(&sound_mutex); + for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { + preg = snd_minors[minor]; + if (preg && preg->dev == dev) { + snd_minors[minor] = NULL; + device_del(dev); + kfree(preg); + break; + } + } + mutex_unlock(&sound_mutex); + if (minor >= ARRAY_SIZE(snd_minors)) + return -ENOENT; + return 0; +} +EXPORT_SYMBOL(snd_unregister_device); + +#ifdef CONFIG_SND_PROC_FS +/* + * INFO PART + */ +static const char *snd_device_type_name(int type) +{ + switch (type) { + case SNDRV_DEVICE_TYPE_CONTROL: + return "control"; + case SNDRV_DEVICE_TYPE_HWDEP: + return "hardware dependent"; + case SNDRV_DEVICE_TYPE_RAWMIDI: + return "raw midi"; + case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: + return "digital audio playback"; + case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + return "digital audio capture"; + case SNDRV_DEVICE_TYPE_SEQUENCER: + return "sequencer"; + case SNDRV_DEVICE_TYPE_TIMER: + return "timer"; + case SNDRV_DEVICE_TYPE_COMPRESS: + return "compress"; + default: + return "?"; + } +} + +static void snd_minor_info_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + int minor; + struct snd_minor *mptr; + + mutex_lock(&sound_mutex); + for (minor = 0; minor < SNDRV_OS_MINORS; ++minor) { + mptr = snd_minors[minor]; + if (!mptr) + continue; + if (mptr->card >= 0) { + if (mptr->device >= 0) + snd_iprintf(buffer, "%3i: [%2i-%2i]: %s\n", + minor, mptr->card, mptr->device, + snd_device_type_name(mptr->type)); + else + snd_iprintf(buffer, "%3i: [%2i] : %s\n", + minor, mptr->card, + snd_device_type_name(mptr->type)); + } else + snd_iprintf(buffer, "%3i: : %s\n", minor, + snd_device_type_name(mptr->type)); + } + mutex_unlock(&sound_mutex); +} + +int __init snd_minor_info_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "devices", NULL); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_minor_info_read; + return snd_info_register(entry); /* freed in error path */ +} +#endif /* CONFIG_SND_PROC_FS */ + +/* + * INIT PART + */ + +static int __init alsa_sound_init(void) +{ + snd_major = major; + snd_ecards_limit = cards_limit; + if (register_chrdev(major, "alsa", &snd_fops)) { + pr_err("ALSA core: unable to register native major device number %d\n", major); + return -EIO; + } + if (snd_info_init() < 0) { + unregister_chrdev(major, "alsa"); + return -ENOMEM; + } + +#ifdef CONFIG_SND_DEBUG + sound_debugfs_root = debugfs_create_dir("sound", NULL); +#endif +#ifndef MODULE + pr_info("Advanced Linux Sound Architecture Driver Initialized.\n"); +#endif + return 0; +} + +static void __exit alsa_sound_exit(void) +{ +#ifdef CONFIG_SND_DEBUG + debugfs_remove(sound_debugfs_root); +#endif + snd_info_done(); + unregister_chrdev(major, "alsa"); +} + +subsys_initcall(alsa_sound_init); +module_exit(alsa_sound_exit); diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c new file mode 100644 index 0000000000..2751bf2ff6 --- /dev/null +++ b/sound/core/sound_oss.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Advanced Linux Sound Architecture + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/minors.h> +#include <sound/info.h> +#include <linux/sound.h> +#include <linux/mutex.h> + +#define SNDRV_OSS_MINORS 256 + +static struct snd_minor *snd_oss_minors[SNDRV_OSS_MINORS]; +static DEFINE_MUTEX(sound_oss_mutex); + +/* NOTE: This function increments the refcount of the associated card like + * snd_lookup_minor_data(); the caller must call snd_card_unref() appropriately + */ +void *snd_lookup_oss_minor_data(unsigned int minor, int type) +{ + struct snd_minor *mreg; + void *private_data; + + if (minor >= ARRAY_SIZE(snd_oss_minors)) + return NULL; + mutex_lock(&sound_oss_mutex); + mreg = snd_oss_minors[minor]; + if (mreg && mreg->type == type) { + private_data = mreg->private_data; + if (private_data && mreg->card_ptr) + get_device(&mreg->card_ptr->card_dev); + } else + private_data = NULL; + mutex_unlock(&sound_oss_mutex); + return private_data; +} +EXPORT_SYMBOL(snd_lookup_oss_minor_data); + +static int snd_oss_kernel_minor(int type, struct snd_card *card, int dev) +{ + int minor; + + switch (type) { + case SNDRV_OSS_DEVICE_TYPE_MIXER: + if (snd_BUG_ON(!card || dev < 0 || dev > 1)) + return -EINVAL; + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIXER1 : SNDRV_MINOR_OSS_MIXER)); + break; + case SNDRV_OSS_DEVICE_TYPE_SEQUENCER: + minor = SNDRV_MINOR_OSS_SEQUENCER; + break; + case SNDRV_OSS_DEVICE_TYPE_MUSIC: + minor = SNDRV_MINOR_OSS_MUSIC; + break; + case SNDRV_OSS_DEVICE_TYPE_PCM: + if (snd_BUG_ON(!card || dev < 0 || dev > 1)) + return -EINVAL; + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_PCM1 : SNDRV_MINOR_OSS_PCM)); + break; + case SNDRV_OSS_DEVICE_TYPE_MIDI: + if (snd_BUG_ON(!card || dev < 0 || dev > 1)) + return -EINVAL; + minor = SNDRV_MINOR_OSS(card->number, (dev ? SNDRV_MINOR_OSS_MIDI1 : SNDRV_MINOR_OSS_MIDI)); + break; + case SNDRV_OSS_DEVICE_TYPE_DMFM: + minor = SNDRV_MINOR_OSS(card->number, SNDRV_MINOR_OSS_DMFM); + break; + case SNDRV_OSS_DEVICE_TYPE_SNDSTAT: + minor = SNDRV_MINOR_OSS_SNDSTAT; + break; + default: + return -EINVAL; + } + if (minor < 0 || minor >= SNDRV_OSS_MINORS) + return -EINVAL; + return minor; +} + +int snd_register_oss_device(int type, struct snd_card *card, int dev, + const struct file_operations *f_ops, void *private_data) +{ + int minor = snd_oss_kernel_minor(type, card, dev); + int minor_unit; + struct snd_minor *preg; + int cidx = SNDRV_MINOR_OSS_CARD(minor); + int track2 = -1; + int register1 = -1, register2 = -1; + struct device *carddev = snd_card_get_device_link(card); + + if (card && card->number >= SNDRV_MINOR_OSS_DEVICES) + return 0; /* ignore silently */ + if (minor < 0) + return minor; + preg = kmalloc(sizeof(struct snd_minor), GFP_KERNEL); + if (preg == NULL) + return -ENOMEM; + preg->type = type; + preg->card = card ? card->number : -1; + preg->device = dev; + preg->f_ops = f_ops; + preg->private_data = private_data; + preg->card_ptr = card; + mutex_lock(&sound_oss_mutex); + snd_oss_minors[minor] = preg; + minor_unit = SNDRV_MINOR_OSS_DEVICE(minor); + switch (minor_unit) { + case SNDRV_MINOR_OSS_PCM: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO); + break; + case SNDRV_MINOR_OSS_MIDI: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI); + break; + case SNDRV_MINOR_OSS_MIDI1: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1); + break; + } + register1 = register_sound_special_device(f_ops, minor, carddev); + if (register1 != minor) + goto __end; + if (track2 >= 0) { + register2 = register_sound_special_device(f_ops, track2, + carddev); + if (register2 != track2) + goto __end; + snd_oss_minors[track2] = preg; + } + mutex_unlock(&sound_oss_mutex); + return 0; + + __end: + if (register2 >= 0) + unregister_sound_special(register2); + if (register1 >= 0) + unregister_sound_special(register1); + snd_oss_minors[minor] = NULL; + mutex_unlock(&sound_oss_mutex); + kfree(preg); + return -EBUSY; +} +EXPORT_SYMBOL(snd_register_oss_device); + +int snd_unregister_oss_device(int type, struct snd_card *card, int dev) +{ + int minor = snd_oss_kernel_minor(type, card, dev); + int cidx = SNDRV_MINOR_OSS_CARD(minor); + int track2 = -1; + struct snd_minor *mptr; + + if (card && card->number >= SNDRV_MINOR_OSS_DEVICES) + return 0; + if (minor < 0) + return minor; + mutex_lock(&sound_oss_mutex); + mptr = snd_oss_minors[minor]; + if (mptr == NULL) { + mutex_unlock(&sound_oss_mutex); + return -ENOENT; + } + switch (SNDRV_MINOR_OSS_DEVICE(minor)) { + case SNDRV_MINOR_OSS_PCM: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_AUDIO); + break; + case SNDRV_MINOR_OSS_MIDI: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI); + break; + case SNDRV_MINOR_OSS_MIDI1: + track2 = SNDRV_MINOR_OSS(cidx, SNDRV_MINOR_OSS_DMMIDI1); + break; + } + if (track2 >= 0) + snd_oss_minors[track2] = NULL; + snd_oss_minors[minor] = NULL; + mutex_unlock(&sound_oss_mutex); + + /* call unregister_sound_special() outside sound_oss_mutex; + * otherwise may deadlock, as it can trigger the release of a card + */ + unregister_sound_special(minor); + if (track2 >= 0) + unregister_sound_special(track2); + + kfree(mptr); + return 0; +} +EXPORT_SYMBOL(snd_unregister_oss_device); + +/* + * INFO PART + */ + +#ifdef CONFIG_SND_PROC_FS +static const char *snd_oss_device_type_name(int type) +{ + switch (type) { + case SNDRV_OSS_DEVICE_TYPE_MIXER: + return "mixer"; + case SNDRV_OSS_DEVICE_TYPE_SEQUENCER: + case SNDRV_OSS_DEVICE_TYPE_MUSIC: + return "sequencer"; + case SNDRV_OSS_DEVICE_TYPE_PCM: + return "digital audio"; + case SNDRV_OSS_DEVICE_TYPE_MIDI: + return "raw midi"; + case SNDRV_OSS_DEVICE_TYPE_DMFM: + return "hardware dependent"; + default: + return "?"; + } +} + +static void snd_minor_info_oss_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + int minor; + struct snd_minor *mptr; + + mutex_lock(&sound_oss_mutex); + for (minor = 0; minor < SNDRV_OSS_MINORS; ++minor) { + mptr = snd_oss_minors[minor]; + if (!mptr) + continue; + if (mptr->card >= 0) + snd_iprintf(buffer, "%3i: [%i-%2i]: %s\n", minor, + mptr->card, mptr->device, + snd_oss_device_type_name(mptr->type)); + else + snd_iprintf(buffer, "%3i: : %s\n", minor, + snd_oss_device_type_name(mptr->type)); + } + mutex_unlock(&sound_oss_mutex); +} + + +int __init snd_minor_info_oss_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "devices", snd_oss_root); + if (!entry) + return -ENOMEM; + entry->c.text.read = snd_minor_info_oss_read; + return snd_info_register(entry); /* freed in error path */ +} +#endif /* CONFIG_SND_PROC_FS */ diff --git a/sound/core/timer.c b/sound/core/timer.c new file mode 100644 index 0000000000..e6e551d4a2 --- /dev/null +++ b/sound/core/timer.c @@ -0,0 +1,2358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Timers abstract layer + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/mutex.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/sched/signal.h> +#include <sound/core.h> +#include <sound/timer.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/minors.h> +#include <sound/initval.h> +#include <linux/kmod.h> + +/* internal flags */ +#define SNDRV_TIMER_IFLG_PAUSED 0x00010000 +#define SNDRV_TIMER_IFLG_DEAD 0x00020000 + +#if IS_ENABLED(CONFIG_SND_HRTIMER) +#define DEFAULT_TIMER_LIMIT 4 +#else +#define DEFAULT_TIMER_LIMIT 1 +#endif + +static int timer_limit = DEFAULT_TIMER_LIMIT; +static int timer_tstamp_monotonic = 1; +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.de>"); +MODULE_DESCRIPTION("ALSA timer interface"); +MODULE_LICENSE("GPL"); +module_param(timer_limit, int, 0444); +MODULE_PARM_DESC(timer_limit, "Maximum global timers in system."); +module_param(timer_tstamp_monotonic, int, 0444); +MODULE_PARM_DESC(timer_tstamp_monotonic, "Use posix monotonic clock source for timestamps (default)."); + +MODULE_ALIAS_CHARDEV(CONFIG_SND_MAJOR, SNDRV_MINOR_TIMER); +MODULE_ALIAS("devname:snd/timer"); + +enum timer_tread_format { + TREAD_FORMAT_NONE = 0, + TREAD_FORMAT_TIME64, + TREAD_FORMAT_TIME32, +}; + +struct snd_timer_tread32 { + int event; + s32 tstamp_sec; + s32 tstamp_nsec; + unsigned int val; +}; + +struct snd_timer_tread64 { + int event; + u8 pad1[4]; + s64 tstamp_sec; + s64 tstamp_nsec; + unsigned int val; + u8 pad2[4]; +}; + +struct snd_timer_user { + struct snd_timer_instance *timeri; + int tread; /* enhanced read with timestamps and events */ + unsigned long ticks; + unsigned long overrun; + int qhead; + int qtail; + int qused; + int queue_size; + bool disconnected; + struct snd_timer_read *queue; + struct snd_timer_tread64 *tqueue; + spinlock_t qlock; + unsigned long last_resolution; + unsigned int filter; + struct timespec64 tstamp; /* trigger tstamp */ + wait_queue_head_t qchange_sleep; + struct snd_fasync *fasync; + struct mutex ioctl_lock; +}; + +struct snd_timer_status32 { + s32 tstamp_sec; /* Timestamp - last update */ + s32 tstamp_nsec; + unsigned int resolution; /* current period resolution in ns */ + unsigned int lost; /* counter of master tick lost */ + unsigned int overrun; /* count of read queue overruns */ + unsigned int queue; /* used queue size */ + unsigned char reserved[64]; /* reserved */ +}; + +#define SNDRV_TIMER_IOCTL_STATUS32 _IOR('T', 0x14, struct snd_timer_status32) + +struct snd_timer_status64 { + s64 tstamp_sec; /* Timestamp - last update */ + s64 tstamp_nsec; + unsigned int resolution; /* current period resolution in ns */ + unsigned int lost; /* counter of master tick lost */ + unsigned int overrun; /* count of read queue overruns */ + unsigned int queue; /* used queue size */ + unsigned char reserved[64]; /* reserved */ +}; + +#define SNDRV_TIMER_IOCTL_STATUS64 _IOR('T', 0x14, struct snd_timer_status64) + +/* list of timers */ +static LIST_HEAD(snd_timer_list); + +/* list of slave instances */ +static LIST_HEAD(snd_timer_slave_list); + +/* lock for slave active lists */ +static DEFINE_SPINLOCK(slave_active_lock); + +#define MAX_SLAVE_INSTANCES 1000 +static int num_slaves; + +static DEFINE_MUTEX(register_mutex); + +static int snd_timer_free(struct snd_timer *timer); +static int snd_timer_dev_free(struct snd_device *device); +static int snd_timer_dev_register(struct snd_device *device); +static int snd_timer_dev_disconnect(struct snd_device *device); + +static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_left); + +/* + * create a timer instance with the given owner string. + */ +struct snd_timer_instance *snd_timer_instance_new(const char *owner) +{ + struct snd_timer_instance *timeri; + + timeri = kzalloc(sizeof(*timeri), GFP_KERNEL); + if (timeri == NULL) + return NULL; + timeri->owner = kstrdup(owner, GFP_KERNEL); + if (! timeri->owner) { + kfree(timeri); + return NULL; + } + INIT_LIST_HEAD(&timeri->open_list); + INIT_LIST_HEAD(&timeri->active_list); + INIT_LIST_HEAD(&timeri->ack_list); + INIT_LIST_HEAD(&timeri->slave_list_head); + INIT_LIST_HEAD(&timeri->slave_active_head); + + return timeri; +} +EXPORT_SYMBOL(snd_timer_instance_new); + +void snd_timer_instance_free(struct snd_timer_instance *timeri) +{ + if (timeri) { + if (timeri->private_free) + timeri->private_free(timeri); + kfree(timeri->owner); + kfree(timeri); + } +} +EXPORT_SYMBOL(snd_timer_instance_free); + +/* + * find a timer instance from the given timer id + */ +static struct snd_timer *snd_timer_find(struct snd_timer_id *tid) +{ + struct snd_timer *timer; + + list_for_each_entry(timer, &snd_timer_list, device_list) { + if (timer->tmr_class != tid->dev_class) + continue; + if ((timer->tmr_class == SNDRV_TIMER_CLASS_CARD || + timer->tmr_class == SNDRV_TIMER_CLASS_PCM) && + (timer->card == NULL || + timer->card->number != tid->card)) + continue; + if (timer->tmr_device != tid->device) + continue; + if (timer->tmr_subdevice != tid->subdevice) + continue; + return timer; + } + return NULL; +} + +#ifdef CONFIG_MODULES + +static void snd_timer_request(struct snd_timer_id *tid) +{ + switch (tid->dev_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + if (tid->device < timer_limit) + request_module("snd-timer-%i", tid->device); + break; + case SNDRV_TIMER_CLASS_CARD: + case SNDRV_TIMER_CLASS_PCM: + if (tid->card < snd_ecards_limit) + request_module("snd-card-%i", tid->card); + break; + default: + break; + } +} + +#endif + +/* move the slave if it belongs to the master; return 1 if match */ +static int check_matching_master_slave(struct snd_timer_instance *master, + struct snd_timer_instance *slave) +{ + if (slave->slave_class != master->slave_class || + slave->slave_id != master->slave_id) + return 0; + if (master->timer->num_instances >= master->timer->max_instances) + return -EBUSY; + list_move_tail(&slave->open_list, &master->slave_list_head); + master->timer->num_instances++; + spin_lock_irq(&slave_active_lock); + spin_lock(&master->timer->lock); + slave->master = master; + slave->timer = master->timer; + if (slave->flags & SNDRV_TIMER_IFLG_RUNNING) + list_add_tail(&slave->active_list, &master->slave_active_head); + spin_unlock(&master->timer->lock); + spin_unlock_irq(&slave_active_lock); + return 1; +} + +/* + * look for a master instance matching with the slave id of the given slave. + * when found, relink the open_link of the slave. + * + * call this with register_mutex down. + */ +static int snd_timer_check_slave(struct snd_timer_instance *slave) +{ + struct snd_timer *timer; + struct snd_timer_instance *master; + int err = 0; + + /* FIXME: it's really dumb to look up all entries.. */ + list_for_each_entry(timer, &snd_timer_list, device_list) { + list_for_each_entry(master, &timer->open_list_head, open_list) { + err = check_matching_master_slave(master, slave); + if (err != 0) /* match found or error */ + goto out; + } + } + out: + return err < 0 ? err : 0; +} + +/* + * look for slave instances matching with the slave id of the given master. + * when found, relink the open_link of slaves. + * + * call this with register_mutex down. + */ +static int snd_timer_check_master(struct snd_timer_instance *master) +{ + struct snd_timer_instance *slave, *tmp; + int err = 0; + + /* check all pending slaves */ + list_for_each_entry_safe(slave, tmp, &snd_timer_slave_list, open_list) { + err = check_matching_master_slave(master, slave); + if (err < 0) + break; + } + return err < 0 ? err : 0; +} + +static void snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put); + +/* + * open a timer instance + * when opening a master, the slave id must be here given. + */ +int snd_timer_open(struct snd_timer_instance *timeri, + struct snd_timer_id *tid, + unsigned int slave_id) +{ + struct snd_timer *timer; + struct device *card_dev_to_put = NULL; + int err; + + mutex_lock(®ister_mutex); + if (tid->dev_class == SNDRV_TIMER_CLASS_SLAVE) { + /* open a slave instance */ + if (tid->dev_sclass <= SNDRV_TIMER_SCLASS_NONE || + tid->dev_sclass > SNDRV_TIMER_SCLASS_OSS_SEQUENCER) { + pr_debug("ALSA: timer: invalid slave class %i\n", + tid->dev_sclass); + err = -EINVAL; + goto unlock; + } + if (num_slaves >= MAX_SLAVE_INSTANCES) { + err = -EBUSY; + goto unlock; + } + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = tid->device; + timeri->flags |= SNDRV_TIMER_IFLG_SLAVE; + list_add_tail(&timeri->open_list, &snd_timer_slave_list); + num_slaves++; + err = snd_timer_check_slave(timeri); + goto list_added; + } + + /* open a master instance */ + timer = snd_timer_find(tid); +#ifdef CONFIG_MODULES + if (!timer) { + mutex_unlock(®ister_mutex); + snd_timer_request(tid); + mutex_lock(®ister_mutex); + timer = snd_timer_find(tid); + } +#endif + if (!timer) { + err = -ENODEV; + goto unlock; + } + if (!list_empty(&timer->open_list_head)) { + struct snd_timer_instance *t = + list_entry(timer->open_list_head.next, + struct snd_timer_instance, open_list); + if (t->flags & SNDRV_TIMER_IFLG_EXCLUSIVE) { + err = -EBUSY; + goto unlock; + } + } + if (timer->num_instances >= timer->max_instances) { + err = -EBUSY; + goto unlock; + } + if (!try_module_get(timer->module)) { + err = -EBUSY; + goto unlock; + } + /* take a card refcount for safe disconnection */ + if (timer->card) { + get_device(&timer->card->card_dev); + card_dev_to_put = &timer->card->card_dev; + } + + if (list_empty(&timer->open_list_head) && timer->hw.open) { + err = timer->hw.open(timer); + if (err) { + module_put(timer->module); + goto unlock; + } + } + + timeri->timer = timer; + timeri->slave_class = tid->dev_sclass; + timeri->slave_id = slave_id; + + list_add_tail(&timeri->open_list, &timer->open_list_head); + timer->num_instances++; + err = snd_timer_check_master(timeri); +list_added: + if (err < 0) + snd_timer_close_locked(timeri, &card_dev_to_put); + + unlock: + mutex_unlock(®ister_mutex); + /* put_device() is called after unlock for avoiding deadlock */ + if (err < 0 && card_dev_to_put) + put_device(card_dev_to_put); + return err; +} +EXPORT_SYMBOL(snd_timer_open); + +/* + * close a timer instance + * call this with register_mutex down. + */ +static void snd_timer_close_locked(struct snd_timer_instance *timeri, + struct device **card_devp_to_put) +{ + struct snd_timer *timer = timeri->timer; + struct snd_timer_instance *slave, *tmp; + + if (timer) { + spin_lock_irq(&timer->lock); + timeri->flags |= SNDRV_TIMER_IFLG_DEAD; + spin_unlock_irq(&timer->lock); + } + + if (!list_empty(&timeri->open_list)) { + list_del_init(&timeri->open_list); + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + num_slaves--; + } + + /* force to stop the timer */ + snd_timer_stop(timeri); + + if (timer) { + timer->num_instances--; + /* wait, until the active callback is finished */ + spin_lock_irq(&timer->lock); + while (timeri->flags & SNDRV_TIMER_IFLG_CALLBACK) { + spin_unlock_irq(&timer->lock); + udelay(10); + spin_lock_irq(&timer->lock); + } + spin_unlock_irq(&timer->lock); + + /* remove slave links */ + spin_lock_irq(&slave_active_lock); + spin_lock(&timer->lock); + timeri->timer = NULL; + list_for_each_entry_safe(slave, tmp, &timeri->slave_list_head, + open_list) { + list_move_tail(&slave->open_list, &snd_timer_slave_list); + timer->num_instances--; + slave->master = NULL; + slave->timer = NULL; + list_del_init(&slave->ack_list); + list_del_init(&slave->active_list); + } + spin_unlock(&timer->lock); + spin_unlock_irq(&slave_active_lock); + + /* slave doesn't need to release timer resources below */ + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + timer = NULL; + } + + if (timer) { + if (list_empty(&timer->open_list_head) && timer->hw.close) + timer->hw.close(timer); + /* release a card refcount for safe disconnection */ + if (timer->card) + *card_devp_to_put = &timer->card->card_dev; + module_put(timer->module); + } +} + +/* + * close a timer instance + */ +void snd_timer_close(struct snd_timer_instance *timeri) +{ + struct device *card_dev_to_put = NULL; + + if (snd_BUG_ON(!timeri)) + return; + + mutex_lock(®ister_mutex); + snd_timer_close_locked(timeri, &card_dev_to_put); + mutex_unlock(®ister_mutex); + /* put_device() is called after unlock for avoiding deadlock */ + if (card_dev_to_put) + put_device(card_dev_to_put); +} +EXPORT_SYMBOL(snd_timer_close); + +static unsigned long snd_timer_hw_resolution(struct snd_timer *timer) +{ + if (timer->hw.c_resolution) + return timer->hw.c_resolution(timer); + else + return timer->hw.resolution; +} + +unsigned long snd_timer_resolution(struct snd_timer_instance *timeri) +{ + struct snd_timer * timer; + unsigned long ret = 0; + unsigned long flags; + + if (timeri == NULL) + return 0; + timer = timeri->timer; + if (timer) { + spin_lock_irqsave(&timer->lock, flags); + ret = snd_timer_hw_resolution(timer); + spin_unlock_irqrestore(&timer->lock, flags); + } + return ret; +} +EXPORT_SYMBOL(snd_timer_resolution); + +static void snd_timer_notify1(struct snd_timer_instance *ti, int event) +{ + struct snd_timer *timer = ti->timer; + unsigned long resolution = 0; + struct snd_timer_instance *ts; + struct timespec64 tstamp; + + if (timer_tstamp_monotonic) + ktime_get_ts64(&tstamp); + else + ktime_get_real_ts64(&tstamp); + if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_START || + event > SNDRV_TIMER_EVENT_PAUSE)) + return; + if (timer && + (event == SNDRV_TIMER_EVENT_START || + event == SNDRV_TIMER_EVENT_CONTINUE)) + resolution = snd_timer_hw_resolution(timer); + if (ti->ccallback) + ti->ccallback(ti, event, &tstamp, resolution); + if (ti->flags & SNDRV_TIMER_IFLG_SLAVE) + return; + if (timer == NULL) + return; + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + return; + event += 10; /* convert to SNDRV_TIMER_EVENT_MXXX */ + list_for_each_entry(ts, &ti->slave_active_head, active_list) + if (ts->ccallback) + ts->ccallback(ts, event, &tstamp, resolution); +} + +/* start/continue a master timer */ +static int snd_timer_start1(struct snd_timer_instance *timeri, + bool start, unsigned long ticks) +{ + struct snd_timer *timer; + int result; + unsigned long flags; + + timer = timeri->timer; + if (!timer) + return -EINVAL; + + spin_lock_irqsave(&timer->lock, flags); + if (timeri->flags & SNDRV_TIMER_IFLG_DEAD) { + result = -EINVAL; + goto unlock; + } + if (timer->card && timer->card->shutdown) { + result = -ENODEV; + goto unlock; + } + if (timeri->flags & (SNDRV_TIMER_IFLG_RUNNING | + SNDRV_TIMER_IFLG_START)) { + result = -EBUSY; + goto unlock; + } + + if (start) + timeri->ticks = timeri->cticks = ticks; + else if (!timeri->cticks) + timeri->cticks = 1; + timeri->pticks = 0; + + list_move_tail(&timeri->active_list, &timer->active_list_head); + if (timer->running) { + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + goto __start_now; + timer->flags |= SNDRV_TIMER_FLG_RESCHED; + timeri->flags |= SNDRV_TIMER_IFLG_START; + result = 1; /* delayed start */ + } else { + if (start) + timer->sticks = ticks; + timer->hw.start(timer); + __start_now: + timer->running++; + timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; + result = 0; + } + snd_timer_notify1(timeri, start ? SNDRV_TIMER_EVENT_START : + SNDRV_TIMER_EVENT_CONTINUE); + unlock: + spin_unlock_irqrestore(&timer->lock, flags); + return result; +} + +/* start/continue a slave timer */ +static int snd_timer_start_slave(struct snd_timer_instance *timeri, + bool start) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&slave_active_lock, flags); + if (timeri->flags & SNDRV_TIMER_IFLG_DEAD) { + err = -EINVAL; + goto unlock; + } + if (timeri->flags & SNDRV_TIMER_IFLG_RUNNING) { + err = -EBUSY; + goto unlock; + } + timeri->flags |= SNDRV_TIMER_IFLG_RUNNING; + if (timeri->master && timeri->timer) { + spin_lock(&timeri->timer->lock); + list_add_tail(&timeri->active_list, + &timeri->master->slave_active_head); + snd_timer_notify1(timeri, start ? SNDRV_TIMER_EVENT_START : + SNDRV_TIMER_EVENT_CONTINUE); + spin_unlock(&timeri->timer->lock); + } + err = 1; /* delayed start */ + unlock: + spin_unlock_irqrestore(&slave_active_lock, flags); + return err; +} + +/* stop/pause a master timer */ +static int snd_timer_stop1(struct snd_timer_instance *timeri, bool stop) +{ + struct snd_timer *timer; + int result = 0; + unsigned long flags; + + timer = timeri->timer; + if (!timer) + return -EINVAL; + spin_lock_irqsave(&timer->lock, flags); + list_del_init(&timeri->ack_list); + list_del_init(&timeri->active_list); + if (!(timeri->flags & (SNDRV_TIMER_IFLG_RUNNING | + SNDRV_TIMER_IFLG_START))) { + result = -EBUSY; + goto unlock; + } + if (timer->card && timer->card->shutdown) + goto unlock; + if (stop) { + timeri->cticks = timeri->ticks; + timeri->pticks = 0; + } + if ((timeri->flags & SNDRV_TIMER_IFLG_RUNNING) && + !(--timer->running)) { + timer->hw.stop(timer); + if (timer->flags & SNDRV_TIMER_FLG_RESCHED) { + timer->flags &= ~SNDRV_TIMER_FLG_RESCHED; + snd_timer_reschedule(timer, 0); + if (timer->flags & SNDRV_TIMER_FLG_CHANGE) { + timer->flags &= ~SNDRV_TIMER_FLG_CHANGE; + timer->hw.start(timer); + } + } + } + timeri->flags &= ~(SNDRV_TIMER_IFLG_RUNNING | SNDRV_TIMER_IFLG_START); + if (stop) + timeri->flags &= ~SNDRV_TIMER_IFLG_PAUSED; + else + timeri->flags |= SNDRV_TIMER_IFLG_PAUSED; + snd_timer_notify1(timeri, stop ? SNDRV_TIMER_EVENT_STOP : + SNDRV_TIMER_EVENT_PAUSE); + unlock: + spin_unlock_irqrestore(&timer->lock, flags); + return result; +} + +/* stop/pause a slave timer */ +static int snd_timer_stop_slave(struct snd_timer_instance *timeri, bool stop) +{ + unsigned long flags; + bool running; + + spin_lock_irqsave(&slave_active_lock, flags); + running = timeri->flags & SNDRV_TIMER_IFLG_RUNNING; + timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING; + if (timeri->timer) { + spin_lock(&timeri->timer->lock); + list_del_init(&timeri->ack_list); + list_del_init(&timeri->active_list); + if (running) + snd_timer_notify1(timeri, stop ? SNDRV_TIMER_EVENT_STOP : + SNDRV_TIMER_EVENT_PAUSE); + spin_unlock(&timeri->timer->lock); + } + spin_unlock_irqrestore(&slave_active_lock, flags); + return running ? 0 : -EBUSY; +} + +/* + * start the timer instance + */ +int snd_timer_start(struct snd_timer_instance *timeri, unsigned int ticks) +{ + if (timeri == NULL || ticks < 1) + return -EINVAL; + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_start_slave(timeri, true); + else + return snd_timer_start1(timeri, true, ticks); +} +EXPORT_SYMBOL(snd_timer_start); + +/* + * stop the timer instance. + * + * do not call this from the timer callback! + */ +int snd_timer_stop(struct snd_timer_instance *timeri) +{ + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_stop_slave(timeri, true); + else + return snd_timer_stop1(timeri, true); +} +EXPORT_SYMBOL(snd_timer_stop); + +/* + * start again.. the tick is kept. + */ +int snd_timer_continue(struct snd_timer_instance *timeri) +{ + /* timer can continue only after pause */ + if (!(timeri->flags & SNDRV_TIMER_IFLG_PAUSED)) + return -EINVAL; + + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_start_slave(timeri, false); + else + return snd_timer_start1(timeri, false, 0); +} +EXPORT_SYMBOL(snd_timer_continue); + +/* + * pause.. remember the ticks left + */ +int snd_timer_pause(struct snd_timer_instance * timeri) +{ + if (timeri->flags & SNDRV_TIMER_IFLG_SLAVE) + return snd_timer_stop_slave(timeri, false); + else + return snd_timer_stop1(timeri, false); +} +EXPORT_SYMBOL(snd_timer_pause); + +/* + * reschedule the timer + * + * start pending instances and check the scheduling ticks. + * when the scheduling ticks is changed set CHANGE flag to reprogram the timer. + */ +static void snd_timer_reschedule(struct snd_timer * timer, unsigned long ticks_left) +{ + struct snd_timer_instance *ti; + unsigned long ticks = ~0UL; + + list_for_each_entry(ti, &timer->active_list_head, active_list) { + if (ti->flags & SNDRV_TIMER_IFLG_START) { + ti->flags &= ~SNDRV_TIMER_IFLG_START; + ti->flags |= SNDRV_TIMER_IFLG_RUNNING; + timer->running++; + } + if (ti->flags & SNDRV_TIMER_IFLG_RUNNING) { + if (ticks > ti->cticks) + ticks = ti->cticks; + } + } + if (ticks == ~0UL) { + timer->flags &= ~SNDRV_TIMER_FLG_RESCHED; + return; + } + if (ticks > timer->hw.ticks) + ticks = timer->hw.ticks; + if (ticks_left != ticks) + timer->flags |= SNDRV_TIMER_FLG_CHANGE; + timer->sticks = ticks; +} + +/* call callbacks in timer ack list */ +static void snd_timer_process_callbacks(struct snd_timer *timer, + struct list_head *head) +{ + struct snd_timer_instance *ti; + unsigned long resolution, ticks; + + while (!list_empty(head)) { + ti = list_first_entry(head, struct snd_timer_instance, + ack_list); + + /* remove from ack_list and make empty */ + list_del_init(&ti->ack_list); + + if (!(ti->flags & SNDRV_TIMER_IFLG_DEAD)) { + ticks = ti->pticks; + ti->pticks = 0; + resolution = ti->resolution; + ti->flags |= SNDRV_TIMER_IFLG_CALLBACK; + spin_unlock(&timer->lock); + if (ti->callback) + ti->callback(ti, resolution, ticks); + spin_lock(&timer->lock); + ti->flags &= ~SNDRV_TIMER_IFLG_CALLBACK; + } + } +} + +/* clear pending instances from ack list */ +static void snd_timer_clear_callbacks(struct snd_timer *timer, + struct list_head *head) +{ + unsigned long flags; + + spin_lock_irqsave(&timer->lock, flags); + while (!list_empty(head)) + list_del_init(head->next); + spin_unlock_irqrestore(&timer->lock, flags); +} + +/* + * timer work + * + */ +static void snd_timer_work(struct work_struct *work) +{ + struct snd_timer *timer = container_of(work, struct snd_timer, task_work); + unsigned long flags; + + if (timer->card && timer->card->shutdown) { + snd_timer_clear_callbacks(timer, &timer->sack_list_head); + return; + } + + spin_lock_irqsave(&timer->lock, flags); + snd_timer_process_callbacks(timer, &timer->sack_list_head); + spin_unlock_irqrestore(&timer->lock, flags); +} + +/* + * timer interrupt + * + * ticks_left is usually equal to timer->sticks. + * + */ +void snd_timer_interrupt(struct snd_timer * timer, unsigned long ticks_left) +{ + struct snd_timer_instance *ti, *ts, *tmp; + unsigned long resolution; + struct list_head *ack_list_head; + unsigned long flags; + bool use_work = false; + + if (timer == NULL) + return; + + if (timer->card && timer->card->shutdown) { + snd_timer_clear_callbacks(timer, &timer->ack_list_head); + return; + } + + spin_lock_irqsave(&timer->lock, flags); + + /* remember the current resolution */ + resolution = snd_timer_hw_resolution(timer); + + /* loop for all active instances + * Here we cannot use list_for_each_entry because the active_list of a + * processed instance is relinked to done_list_head before the callback + * is called. + */ + list_for_each_entry_safe(ti, tmp, &timer->active_list_head, + active_list) { + if (ti->flags & SNDRV_TIMER_IFLG_DEAD) + continue; + if (!(ti->flags & SNDRV_TIMER_IFLG_RUNNING)) + continue; + ti->pticks += ticks_left; + ti->resolution = resolution; + if (ti->cticks < ticks_left) + ti->cticks = 0; + else + ti->cticks -= ticks_left; + if (ti->cticks) /* not expired */ + continue; + if (ti->flags & SNDRV_TIMER_IFLG_AUTO) { + ti->cticks = ti->ticks; + } else { + ti->flags &= ~SNDRV_TIMER_IFLG_RUNNING; + --timer->running; + list_del_init(&ti->active_list); + } + if ((timer->hw.flags & SNDRV_TIMER_HW_WORK) || + (ti->flags & SNDRV_TIMER_IFLG_FAST)) + ack_list_head = &timer->ack_list_head; + else + ack_list_head = &timer->sack_list_head; + if (list_empty(&ti->ack_list)) + list_add_tail(&ti->ack_list, ack_list_head); + list_for_each_entry(ts, &ti->slave_active_head, active_list) { + ts->pticks = ti->pticks; + ts->resolution = resolution; + if (list_empty(&ts->ack_list)) + list_add_tail(&ts->ack_list, ack_list_head); + } + } + if (timer->flags & SNDRV_TIMER_FLG_RESCHED) + snd_timer_reschedule(timer, timer->sticks); + if (timer->running) { + if (timer->hw.flags & SNDRV_TIMER_HW_STOP) { + timer->hw.stop(timer); + timer->flags |= SNDRV_TIMER_FLG_CHANGE; + } + if (!(timer->hw.flags & SNDRV_TIMER_HW_AUTO) || + (timer->flags & SNDRV_TIMER_FLG_CHANGE)) { + /* restart timer */ + timer->flags &= ~SNDRV_TIMER_FLG_CHANGE; + timer->hw.start(timer); + } + } else { + timer->hw.stop(timer); + } + + /* now process all fast callbacks */ + snd_timer_process_callbacks(timer, &timer->ack_list_head); + + /* do we have any slow callbacks? */ + use_work = !list_empty(&timer->sack_list_head); + spin_unlock_irqrestore(&timer->lock, flags); + + if (use_work) + queue_work(system_highpri_wq, &timer->task_work); +} +EXPORT_SYMBOL(snd_timer_interrupt); + +/* + + */ + +int snd_timer_new(struct snd_card *card, char *id, struct snd_timer_id *tid, + struct snd_timer **rtimer) +{ + struct snd_timer *timer; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_timer_dev_free, + .dev_register = snd_timer_dev_register, + .dev_disconnect = snd_timer_dev_disconnect, + }; + + if (snd_BUG_ON(!tid)) + return -EINVAL; + if (tid->dev_class == SNDRV_TIMER_CLASS_CARD || + tid->dev_class == SNDRV_TIMER_CLASS_PCM) { + if (WARN_ON(!card)) + return -EINVAL; + } + if (rtimer) + *rtimer = NULL; + timer = kzalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) + return -ENOMEM; + timer->tmr_class = tid->dev_class; + timer->card = card; + timer->tmr_device = tid->device; + timer->tmr_subdevice = tid->subdevice; + if (id) + strscpy(timer->id, id, sizeof(timer->id)); + timer->sticks = 1; + INIT_LIST_HEAD(&timer->device_list); + INIT_LIST_HEAD(&timer->open_list_head); + INIT_LIST_HEAD(&timer->active_list_head); + INIT_LIST_HEAD(&timer->ack_list_head); + INIT_LIST_HEAD(&timer->sack_list_head); + spin_lock_init(&timer->lock); + INIT_WORK(&timer->task_work, snd_timer_work); + timer->max_instances = 1000; /* default limit per timer */ + if (card != NULL) { + timer->module = card->module; + err = snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops); + if (err < 0) { + snd_timer_free(timer); + return err; + } + } + if (rtimer) + *rtimer = timer; + return 0; +} +EXPORT_SYMBOL(snd_timer_new); + +static int snd_timer_free(struct snd_timer *timer) +{ + if (!timer) + return 0; + + mutex_lock(®ister_mutex); + if (! list_empty(&timer->open_list_head)) { + struct list_head *p, *n; + struct snd_timer_instance *ti; + pr_warn("ALSA: timer %p is busy?\n", timer); + list_for_each_safe(p, n, &timer->open_list_head) { + list_del_init(p); + ti = list_entry(p, struct snd_timer_instance, open_list); + ti->timer = NULL; + } + } + list_del(&timer->device_list); + mutex_unlock(®ister_mutex); + + if (timer->private_free) + timer->private_free(timer); + kfree(timer); + return 0; +} + +static int snd_timer_dev_free(struct snd_device *device) +{ + struct snd_timer *timer = device->device_data; + return snd_timer_free(timer); +} + +static int snd_timer_dev_register(struct snd_device *dev) +{ + struct snd_timer *timer = dev->device_data; + struct snd_timer *timer1; + + if (snd_BUG_ON(!timer || !timer->hw.start || !timer->hw.stop)) + return -ENXIO; + if (!(timer->hw.flags & SNDRV_TIMER_HW_SLAVE) && + !timer->hw.resolution && timer->hw.c_resolution == NULL) + return -EINVAL; + + mutex_lock(®ister_mutex); + list_for_each_entry(timer1, &snd_timer_list, device_list) { + if (timer1->tmr_class > timer->tmr_class) + break; + if (timer1->tmr_class < timer->tmr_class) + continue; + if (timer1->card && timer->card) { + if (timer1->card->number > timer->card->number) + break; + if (timer1->card->number < timer->card->number) + continue; + } + if (timer1->tmr_device > timer->tmr_device) + break; + if (timer1->tmr_device < timer->tmr_device) + continue; + if (timer1->tmr_subdevice > timer->tmr_subdevice) + break; + if (timer1->tmr_subdevice < timer->tmr_subdevice) + continue; + /* conflicts.. */ + mutex_unlock(®ister_mutex); + return -EBUSY; + } + list_add_tail(&timer->device_list, &timer1->device_list); + mutex_unlock(®ister_mutex); + return 0; +} + +static int snd_timer_dev_disconnect(struct snd_device *device) +{ + struct snd_timer *timer = device->device_data; + struct snd_timer_instance *ti; + + mutex_lock(®ister_mutex); + list_del_init(&timer->device_list); + /* wake up pending sleepers */ + list_for_each_entry(ti, &timer->open_list_head, open_list) { + if (ti->disconnect) + ti->disconnect(ti); + } + mutex_unlock(®ister_mutex); + return 0; +} + +void snd_timer_notify(struct snd_timer *timer, int event, struct timespec64 *tstamp) +{ + unsigned long flags; + unsigned long resolution = 0; + struct snd_timer_instance *ti, *ts; + + if (timer->card && timer->card->shutdown) + return; + if (! (timer->hw.flags & SNDRV_TIMER_HW_SLAVE)) + return; + if (snd_BUG_ON(event < SNDRV_TIMER_EVENT_MSTART || + event > SNDRV_TIMER_EVENT_MRESUME)) + return; + spin_lock_irqsave(&timer->lock, flags); + if (event == SNDRV_TIMER_EVENT_MSTART || + event == SNDRV_TIMER_EVENT_MCONTINUE || + event == SNDRV_TIMER_EVENT_MRESUME) + resolution = snd_timer_hw_resolution(timer); + list_for_each_entry(ti, &timer->active_list_head, active_list) { + if (ti->ccallback) + ti->ccallback(ti, event, tstamp, resolution); + list_for_each_entry(ts, &ti->slave_active_head, active_list) + if (ts->ccallback) + ts->ccallback(ts, event, tstamp, resolution); + } + spin_unlock_irqrestore(&timer->lock, flags); +} +EXPORT_SYMBOL(snd_timer_notify); + +/* + * exported functions for global timers + */ +int snd_timer_global_new(char *id, int device, struct snd_timer **rtimer) +{ + struct snd_timer_id tid; + + tid.dev_class = SNDRV_TIMER_CLASS_GLOBAL; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = -1; + tid.device = device; + tid.subdevice = 0; + return snd_timer_new(NULL, id, &tid, rtimer); +} +EXPORT_SYMBOL(snd_timer_global_new); + +int snd_timer_global_free(struct snd_timer *timer) +{ + return snd_timer_free(timer); +} +EXPORT_SYMBOL(snd_timer_global_free); + +int snd_timer_global_register(struct snd_timer *timer) +{ + struct snd_device dev; + + memset(&dev, 0, sizeof(dev)); + dev.device_data = timer; + return snd_timer_dev_register(&dev); +} +EXPORT_SYMBOL(snd_timer_global_register); + +/* + * System timer + */ + +struct snd_timer_system_private { + struct timer_list tlist; + struct snd_timer *snd_timer; + unsigned long last_expires; + unsigned long last_jiffies; + unsigned long correction; +}; + +static void snd_timer_s_function(struct timer_list *t) +{ + struct snd_timer_system_private *priv = from_timer(priv, t, + tlist); + struct snd_timer *timer = priv->snd_timer; + unsigned long jiff = jiffies; + if (time_after(jiff, priv->last_expires)) + priv->correction += (long)jiff - (long)priv->last_expires; + snd_timer_interrupt(timer, (long)jiff - (long)priv->last_jiffies); +} + +static int snd_timer_s_start(struct snd_timer * timer) +{ + struct snd_timer_system_private *priv; + unsigned long njiff; + + priv = (struct snd_timer_system_private *) timer->private_data; + njiff = (priv->last_jiffies = jiffies); + if (priv->correction > timer->sticks - 1) { + priv->correction -= timer->sticks - 1; + njiff++; + } else { + njiff += timer->sticks - priv->correction; + priv->correction = 0; + } + priv->last_expires = njiff; + mod_timer(&priv->tlist, njiff); + return 0; +} + +static int snd_timer_s_stop(struct snd_timer * timer) +{ + struct snd_timer_system_private *priv; + unsigned long jiff; + + priv = (struct snd_timer_system_private *) timer->private_data; + del_timer(&priv->tlist); + jiff = jiffies; + if (time_before(jiff, priv->last_expires)) + timer->sticks = priv->last_expires - jiff; + else + timer->sticks = 1; + priv->correction = 0; + return 0; +} + +static int snd_timer_s_close(struct snd_timer *timer) +{ + struct snd_timer_system_private *priv; + + priv = (struct snd_timer_system_private *)timer->private_data; + del_timer_sync(&priv->tlist); + return 0; +} + +static const struct snd_timer_hardware snd_timer_system = +{ + .flags = SNDRV_TIMER_HW_FIRST | SNDRV_TIMER_HW_WORK, + .resolution = 1000000000L / HZ, + .ticks = 10000000L, + .close = snd_timer_s_close, + .start = snd_timer_s_start, + .stop = snd_timer_s_stop +}; + +static void snd_timer_free_system(struct snd_timer *timer) +{ + kfree(timer->private_data); +} + +static int snd_timer_register_system(void) +{ + struct snd_timer *timer; + struct snd_timer_system_private *priv; + int err; + + err = snd_timer_global_new("system", SNDRV_TIMER_GLOBAL_SYSTEM, &timer); + if (err < 0) + return err; + strcpy(timer->name, "system timer"); + timer->hw = snd_timer_system; + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + snd_timer_free(timer); + return -ENOMEM; + } + priv->snd_timer = timer; + timer_setup(&priv->tlist, snd_timer_s_function, 0); + timer->private_data = priv; + timer->private_free = snd_timer_free_system; + return snd_timer_global_register(timer); +} + +#ifdef CONFIG_SND_PROC_FS +/* + * Info interface + */ + +static void snd_timer_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_timer *timer; + struct snd_timer_instance *ti; + unsigned long resolution; + + mutex_lock(®ister_mutex); + list_for_each_entry(timer, &snd_timer_list, device_list) { + if (timer->card && timer->card->shutdown) + continue; + switch (timer->tmr_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + snd_iprintf(buffer, "G%i: ", timer->tmr_device); + break; + case SNDRV_TIMER_CLASS_CARD: + snd_iprintf(buffer, "C%i-%i: ", + timer->card->number, timer->tmr_device); + break; + case SNDRV_TIMER_CLASS_PCM: + snd_iprintf(buffer, "P%i-%i-%i: ", timer->card->number, + timer->tmr_device, timer->tmr_subdevice); + break; + default: + snd_iprintf(buffer, "?%i-%i-%i-%i: ", timer->tmr_class, + timer->card ? timer->card->number : -1, + timer->tmr_device, timer->tmr_subdevice); + } + snd_iprintf(buffer, "%s :", timer->name); + spin_lock_irq(&timer->lock); + resolution = snd_timer_hw_resolution(timer); + spin_unlock_irq(&timer->lock); + if (resolution) + snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", + resolution / 1000, + resolution % 1000, + timer->hw.ticks); + if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) + snd_iprintf(buffer, " SLAVE"); + snd_iprintf(buffer, "\n"); + list_for_each_entry(ti, &timer->open_list_head, open_list) + snd_iprintf(buffer, " Client %s : %s\n", + ti->owner ? ti->owner : "unknown", + (ti->flags & (SNDRV_TIMER_IFLG_START | + SNDRV_TIMER_IFLG_RUNNING)) + ? "running" : "stopped"); + } + mutex_unlock(®ister_mutex); +} + +static struct snd_info_entry *snd_timer_proc_entry; + +static void __init snd_timer_proc_init(void) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_module_entry(THIS_MODULE, "timers", NULL); + if (entry != NULL) { + entry->c.text.read = snd_timer_proc_read; + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + entry = NULL; + } + } + snd_timer_proc_entry = entry; +} + +static void __exit snd_timer_proc_done(void) +{ + snd_info_free_entry(snd_timer_proc_entry); +} +#else /* !CONFIG_SND_PROC_FS */ +#define snd_timer_proc_init() +#define snd_timer_proc_done() +#endif + +/* + * USER SPACE interface + */ + +static void snd_timer_user_interrupt(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + struct snd_timer_user *tu = timeri->callback_data; + struct snd_timer_read *r; + int prev; + + spin_lock(&tu->qlock); + if (tu->qused > 0) { + prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1; + r = &tu->queue[prev]; + if (r->resolution == resolution) { + r->ticks += ticks; + goto __wake; + } + } + if (tu->qused >= tu->queue_size) { + tu->overrun++; + } else { + r = &tu->queue[tu->qtail++]; + tu->qtail %= tu->queue_size; + r->resolution = resolution; + r->ticks = ticks; + tu->qused++; + } + __wake: + spin_unlock(&tu->qlock); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_append_to_tqueue(struct snd_timer_user *tu, + struct snd_timer_tread64 *tread) +{ + if (tu->qused >= tu->queue_size) { + tu->overrun++; + } else { + memcpy(&tu->tqueue[tu->qtail++], tread, sizeof(*tread)); + tu->qtail %= tu->queue_size; + tu->qused++; + } +} + +static void snd_timer_user_ccallback(struct snd_timer_instance *timeri, + int event, + struct timespec64 *tstamp, + unsigned long resolution) +{ + struct snd_timer_user *tu = timeri->callback_data; + struct snd_timer_tread64 r1; + unsigned long flags; + + if (event >= SNDRV_TIMER_EVENT_START && + event <= SNDRV_TIMER_EVENT_PAUSE) + tu->tstamp = *tstamp; + if ((tu->filter & (1 << event)) == 0 || !tu->tread) + return; + memset(&r1, 0, sizeof(r1)); + r1.event = event; + r1.tstamp_sec = tstamp->tv_sec; + r1.tstamp_nsec = tstamp->tv_nsec; + r1.val = resolution; + spin_lock_irqsave(&tu->qlock, flags); + snd_timer_user_append_to_tqueue(tu, &r1); + spin_unlock_irqrestore(&tu->qlock, flags); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_disconnect(struct snd_timer_instance *timeri) +{ + struct snd_timer_user *tu = timeri->callback_data; + + tu->disconnected = true; + wake_up(&tu->qchange_sleep); +} + +static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + struct snd_timer_user *tu = timeri->callback_data; + struct snd_timer_tread64 *r, r1; + struct timespec64 tstamp; + int prev, append = 0; + + memset(&r1, 0, sizeof(r1)); + memset(&tstamp, 0, sizeof(tstamp)); + spin_lock(&tu->qlock); + if ((tu->filter & ((1 << SNDRV_TIMER_EVENT_RESOLUTION) | + (1 << SNDRV_TIMER_EVENT_TICK))) == 0) { + spin_unlock(&tu->qlock); + return; + } + if (tu->last_resolution != resolution || ticks > 0) { + if (timer_tstamp_monotonic) + ktime_get_ts64(&tstamp); + else + ktime_get_real_ts64(&tstamp); + } + if ((tu->filter & (1 << SNDRV_TIMER_EVENT_RESOLUTION)) && + tu->last_resolution != resolution) { + r1.event = SNDRV_TIMER_EVENT_RESOLUTION; + r1.tstamp_sec = tstamp.tv_sec; + r1.tstamp_nsec = tstamp.tv_nsec; + r1.val = resolution; + snd_timer_user_append_to_tqueue(tu, &r1); + tu->last_resolution = resolution; + append++; + } + if ((tu->filter & (1 << SNDRV_TIMER_EVENT_TICK)) == 0) + goto __wake; + if (ticks == 0) + goto __wake; + if (tu->qused > 0) { + prev = tu->qtail == 0 ? tu->queue_size - 1 : tu->qtail - 1; + r = &tu->tqueue[prev]; + if (r->event == SNDRV_TIMER_EVENT_TICK) { + r->tstamp_sec = tstamp.tv_sec; + r->tstamp_nsec = tstamp.tv_nsec; + r->val += ticks; + append++; + goto __wake; + } + } + r1.event = SNDRV_TIMER_EVENT_TICK; + r1.tstamp_sec = tstamp.tv_sec; + r1.tstamp_nsec = tstamp.tv_nsec; + r1.val = ticks; + snd_timer_user_append_to_tqueue(tu, &r1); + append++; + __wake: + spin_unlock(&tu->qlock); + if (append == 0) + return; + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); + wake_up(&tu->qchange_sleep); +} + +static int realloc_user_queue(struct snd_timer_user *tu, int size) +{ + struct snd_timer_read *queue = NULL; + struct snd_timer_tread64 *tqueue = NULL; + + if (tu->tread) { + tqueue = kcalloc(size, sizeof(*tqueue), GFP_KERNEL); + if (!tqueue) + return -ENOMEM; + } else { + queue = kcalloc(size, sizeof(*queue), GFP_KERNEL); + if (!queue) + return -ENOMEM; + } + + spin_lock_irq(&tu->qlock); + kfree(tu->queue); + kfree(tu->tqueue); + tu->queue_size = size; + tu->queue = queue; + tu->tqueue = tqueue; + tu->qhead = tu->qtail = tu->qused = 0; + spin_unlock_irq(&tu->qlock); + + return 0; +} + +static int snd_timer_user_open(struct inode *inode, struct file *file) +{ + struct snd_timer_user *tu; + int err; + + err = stream_open(inode, file); + if (err < 0) + return err; + + tu = kzalloc(sizeof(*tu), GFP_KERNEL); + if (tu == NULL) + return -ENOMEM; + spin_lock_init(&tu->qlock); + init_waitqueue_head(&tu->qchange_sleep); + mutex_init(&tu->ioctl_lock); + tu->ticks = 1; + if (realloc_user_queue(tu, 128) < 0) { + kfree(tu); + return -ENOMEM; + } + file->private_data = tu; + return 0; +} + +static int snd_timer_user_release(struct inode *inode, struct file *file) +{ + struct snd_timer_user *tu; + + if (file->private_data) { + tu = file->private_data; + file->private_data = NULL; + mutex_lock(&tu->ioctl_lock); + if (tu->timeri) { + snd_timer_close(tu->timeri); + snd_timer_instance_free(tu->timeri); + } + mutex_unlock(&tu->ioctl_lock); + snd_fasync_free(tu->fasync); + kfree(tu->queue); + kfree(tu->tqueue); + kfree(tu); + } + return 0; +} + +static void snd_timer_user_zero_id(struct snd_timer_id *id) +{ + id->dev_class = SNDRV_TIMER_CLASS_NONE; + id->dev_sclass = SNDRV_TIMER_SCLASS_NONE; + id->card = -1; + id->device = -1; + id->subdevice = -1; +} + +static void snd_timer_user_copy_id(struct snd_timer_id *id, struct snd_timer *timer) +{ + id->dev_class = timer->tmr_class; + id->dev_sclass = SNDRV_TIMER_SCLASS_NONE; + id->card = timer->card ? timer->card->number : -1; + id->device = timer->tmr_device; + id->subdevice = timer->tmr_subdevice; +} + +static int snd_timer_user_next_device(struct snd_timer_id __user *_tid) +{ + struct snd_timer_id id; + struct snd_timer *timer; + struct list_head *p; + + if (copy_from_user(&id, _tid, sizeof(id))) + return -EFAULT; + mutex_lock(®ister_mutex); + if (id.dev_class < 0) { /* first item */ + if (list_empty(&snd_timer_list)) + snd_timer_user_zero_id(&id); + else { + timer = list_entry(snd_timer_list.next, + struct snd_timer, device_list); + snd_timer_user_copy_id(&id, timer); + } + } else { + switch (id.dev_class) { + case SNDRV_TIMER_CLASS_GLOBAL: + id.device = id.device < 0 ? 0 : id.device + 1; + list_for_each(p, &snd_timer_list) { + timer = list_entry(p, struct snd_timer, device_list); + if (timer->tmr_class > SNDRV_TIMER_CLASS_GLOBAL) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_device >= id.device) { + snd_timer_user_copy_id(&id, timer); + break; + } + } + if (p == &snd_timer_list) + snd_timer_user_zero_id(&id); + break; + case SNDRV_TIMER_CLASS_CARD: + case SNDRV_TIMER_CLASS_PCM: + if (id.card < 0) { + id.card = 0; + } else { + if (id.device < 0) { + id.device = 0; + } else { + if (id.subdevice < 0) + id.subdevice = 0; + else if (id.subdevice < INT_MAX) + id.subdevice++; + } + } + list_for_each(p, &snd_timer_list) { + timer = list_entry(p, struct snd_timer, device_list); + if (timer->tmr_class > id.dev_class) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_class < id.dev_class) + continue; + if (timer->card->number > id.card) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->card->number < id.card) + continue; + if (timer->tmr_device > id.device) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_device < id.device) + continue; + if (timer->tmr_subdevice > id.subdevice) { + snd_timer_user_copy_id(&id, timer); + break; + } + if (timer->tmr_subdevice < id.subdevice) + continue; + snd_timer_user_copy_id(&id, timer); + break; + } + if (p == &snd_timer_list) + snd_timer_user_zero_id(&id); + break; + default: + snd_timer_user_zero_id(&id); + } + } + mutex_unlock(®ister_mutex); + if (copy_to_user(_tid, &id, sizeof(*_tid))) + return -EFAULT; + return 0; +} + +static int snd_timer_user_ginfo(struct file *file, + struct snd_timer_ginfo __user *_ginfo) +{ + struct snd_timer_ginfo *ginfo; + struct snd_timer_id tid; + struct snd_timer *t; + struct list_head *p; + int err = 0; + + ginfo = memdup_user(_ginfo, sizeof(*ginfo)); + if (IS_ERR(ginfo)) + return PTR_ERR(ginfo); + + tid = ginfo->tid; + memset(ginfo, 0, sizeof(*ginfo)); + ginfo->tid = tid; + mutex_lock(®ister_mutex); + t = snd_timer_find(&tid); + if (t != NULL) { + ginfo->card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + ginfo->flags |= SNDRV_TIMER_FLG_SLAVE; + strscpy(ginfo->id, t->id, sizeof(ginfo->id)); + strscpy(ginfo->name, t->name, sizeof(ginfo->name)); + spin_lock_irq(&t->lock); + ginfo->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); + if (t->hw.resolution_min > 0) { + ginfo->resolution_min = t->hw.resolution_min; + ginfo->resolution_max = t->hw.resolution_max; + } + list_for_each(p, &t->open_list_head) { + ginfo->clients++; + } + } else { + err = -ENODEV; + } + mutex_unlock(®ister_mutex); + if (err >= 0 && copy_to_user(_ginfo, ginfo, sizeof(*ginfo))) + err = -EFAULT; + kfree(ginfo); + return err; +} + +static int timer_set_gparams(struct snd_timer_gparams *gparams) +{ + struct snd_timer *t; + int err; + + mutex_lock(®ister_mutex); + t = snd_timer_find(&gparams->tid); + if (!t) { + err = -ENODEV; + goto _error; + } + if (!list_empty(&t->open_list_head)) { + err = -EBUSY; + goto _error; + } + if (!t->hw.set_period) { + err = -ENOSYS; + goto _error; + } + err = t->hw.set_period(t, gparams->period_num, gparams->period_den); +_error: + mutex_unlock(®ister_mutex); + return err; +} + +static int snd_timer_user_gparams(struct file *file, + struct snd_timer_gparams __user *_gparams) +{ + struct snd_timer_gparams gparams; + + if (copy_from_user(&gparams, _gparams, sizeof(gparams))) + return -EFAULT; + return timer_set_gparams(&gparams); +} + +static int snd_timer_user_gstatus(struct file *file, + struct snd_timer_gstatus __user *_gstatus) +{ + struct snd_timer_gstatus gstatus; + struct snd_timer_id tid; + struct snd_timer *t; + int err = 0; + + if (copy_from_user(&gstatus, _gstatus, sizeof(gstatus))) + return -EFAULT; + tid = gstatus.tid; + memset(&gstatus, 0, sizeof(gstatus)); + gstatus.tid = tid; + mutex_lock(®ister_mutex); + t = snd_timer_find(&tid); + if (t != NULL) { + spin_lock_irq(&t->lock); + gstatus.resolution = snd_timer_hw_resolution(t); + if (t->hw.precise_resolution) { + t->hw.precise_resolution(t, &gstatus.resolution_num, + &gstatus.resolution_den); + } else { + gstatus.resolution_num = gstatus.resolution; + gstatus.resolution_den = 1000000000uL; + } + spin_unlock_irq(&t->lock); + } else { + err = -ENODEV; + } + mutex_unlock(®ister_mutex); + if (err >= 0 && copy_to_user(_gstatus, &gstatus, sizeof(gstatus))) + err = -EFAULT; + return err; +} + +static int snd_timer_user_tselect(struct file *file, + struct snd_timer_select __user *_tselect) +{ + struct snd_timer_user *tu; + struct snd_timer_select tselect; + char str[32]; + int err = 0; + + tu = file->private_data; + if (tu->timeri) { + snd_timer_close(tu->timeri); + snd_timer_instance_free(tu->timeri); + tu->timeri = NULL; + } + if (copy_from_user(&tselect, _tselect, sizeof(tselect))) { + err = -EFAULT; + goto __err; + } + sprintf(str, "application %i", current->pid); + if (tselect.id.dev_class != SNDRV_TIMER_CLASS_SLAVE) + tselect.id.dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION; + tu->timeri = snd_timer_instance_new(str); + if (!tu->timeri) { + err = -ENOMEM; + goto __err; + } + + tu->timeri->flags |= SNDRV_TIMER_IFLG_FAST; + tu->timeri->callback = tu->tread + ? snd_timer_user_tinterrupt : snd_timer_user_interrupt; + tu->timeri->ccallback = snd_timer_user_ccallback; + tu->timeri->callback_data = (void *)tu; + tu->timeri->disconnect = snd_timer_user_disconnect; + + err = snd_timer_open(tu->timeri, &tselect.id, current->pid); + if (err < 0) { + snd_timer_instance_free(tu->timeri); + tu->timeri = NULL; + } + + __err: + return err; +} + +static int snd_timer_user_info(struct file *file, + struct snd_timer_info __user *_info) +{ + struct snd_timer_user *tu; + struct snd_timer_info *info; + struct snd_timer *t; + int err = 0; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + t = tu->timeri->timer; + if (!t) + return -EBADFD; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (! info) + return -ENOMEM; + info->card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + info->flags |= SNDRV_TIMER_FLG_SLAVE; + strscpy(info->id, t->id, sizeof(info->id)); + strscpy(info->name, t->name, sizeof(info->name)); + spin_lock_irq(&t->lock); + info->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); + if (copy_to_user(_info, info, sizeof(*_info))) + err = -EFAULT; + kfree(info); + return err; +} + +static int snd_timer_user_params(struct file *file, + struct snd_timer_params __user *_params) +{ + struct snd_timer_user *tu; + struct snd_timer_params params; + struct snd_timer *t; + int err; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + t = tu->timeri->timer; + if (!t) + return -EBADFD; + if (copy_from_user(¶ms, _params, sizeof(params))) + return -EFAULT; + if (!(t->hw.flags & SNDRV_TIMER_HW_SLAVE)) { + u64 resolution; + + if (params.ticks < 1) { + err = -EINVAL; + goto _end; + } + + /* Don't allow resolution less than 1ms */ + resolution = snd_timer_resolution(tu->timeri); + resolution *= params.ticks; + if (resolution < 1000000) { + err = -EINVAL; + goto _end; + } + } + if (params.queue_size > 0 && + (params.queue_size < 32 || params.queue_size > 1024)) { + err = -EINVAL; + goto _end; + } + if (params.filter & ~((1<<SNDRV_TIMER_EVENT_RESOLUTION)| + (1<<SNDRV_TIMER_EVENT_TICK)| + (1<<SNDRV_TIMER_EVENT_START)| + (1<<SNDRV_TIMER_EVENT_STOP)| + (1<<SNDRV_TIMER_EVENT_CONTINUE)| + (1<<SNDRV_TIMER_EVENT_PAUSE)| + (1<<SNDRV_TIMER_EVENT_SUSPEND)| + (1<<SNDRV_TIMER_EVENT_RESUME)| + (1<<SNDRV_TIMER_EVENT_MSTART)| + (1<<SNDRV_TIMER_EVENT_MSTOP)| + (1<<SNDRV_TIMER_EVENT_MCONTINUE)| + (1<<SNDRV_TIMER_EVENT_MPAUSE)| + (1<<SNDRV_TIMER_EVENT_MSUSPEND)| + (1<<SNDRV_TIMER_EVENT_MRESUME))) { + err = -EINVAL; + goto _end; + } + snd_timer_stop(tu->timeri); + spin_lock_irq(&t->lock); + tu->timeri->flags &= ~(SNDRV_TIMER_IFLG_AUTO| + SNDRV_TIMER_IFLG_EXCLUSIVE| + SNDRV_TIMER_IFLG_EARLY_EVENT); + if (params.flags & SNDRV_TIMER_PSFLG_AUTO) + tu->timeri->flags |= SNDRV_TIMER_IFLG_AUTO; + if (params.flags & SNDRV_TIMER_PSFLG_EXCLUSIVE) + tu->timeri->flags |= SNDRV_TIMER_IFLG_EXCLUSIVE; + if (params.flags & SNDRV_TIMER_PSFLG_EARLY_EVENT) + tu->timeri->flags |= SNDRV_TIMER_IFLG_EARLY_EVENT; + spin_unlock_irq(&t->lock); + if (params.queue_size > 0 && + (unsigned int)tu->queue_size != params.queue_size) { + err = realloc_user_queue(tu, params.queue_size); + if (err < 0) + goto _end; + } + spin_lock_irq(&tu->qlock); + tu->qhead = tu->qtail = tu->qused = 0; + if (tu->timeri->flags & SNDRV_TIMER_IFLG_EARLY_EVENT) { + if (tu->tread) { + struct snd_timer_tread64 tread; + memset(&tread, 0, sizeof(tread)); + tread.event = SNDRV_TIMER_EVENT_EARLY; + tread.tstamp_sec = 0; + tread.tstamp_nsec = 0; + tread.val = 0; + snd_timer_user_append_to_tqueue(tu, &tread); + } else { + struct snd_timer_read *r = &tu->queue[0]; + r->resolution = 0; + r->ticks = 0; + tu->qused++; + tu->qtail++; + } + } + tu->filter = params.filter; + tu->ticks = params.ticks; + spin_unlock_irq(&tu->qlock); + err = 0; + _end: + if (copy_to_user(_params, ¶ms, sizeof(params))) + return -EFAULT; + return err; +} + +static int snd_timer_user_status32(struct file *file, + struct snd_timer_status32 __user *_status) + { + struct snd_timer_user *tu; + struct snd_timer_status32 status; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + memset(&status, 0, sizeof(status)); + status.tstamp_sec = tu->tstamp.tv_sec; + status.tstamp_nsec = tu->tstamp.tv_nsec; + status.resolution = snd_timer_resolution(tu->timeri); + status.lost = tu->timeri->lost; + status.overrun = tu->overrun; + spin_lock_irq(&tu->qlock); + status.queue = tu->qused; + spin_unlock_irq(&tu->qlock); + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +static int snd_timer_user_status64(struct file *file, + struct snd_timer_status64 __user *_status) +{ + struct snd_timer_user *tu; + struct snd_timer_status64 status; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + memset(&status, 0, sizeof(status)); + status.tstamp_sec = tu->tstamp.tv_sec; + status.tstamp_nsec = tu->tstamp.tv_nsec; + status.resolution = snd_timer_resolution(tu->timeri); + status.lost = tu->timeri->lost; + status.overrun = tu->overrun; + spin_lock_irq(&tu->qlock); + status.queue = tu->qused; + spin_unlock_irq(&tu->qlock); + if (copy_to_user(_status, &status, sizeof(status))) + return -EFAULT; + return 0; +} + +static int snd_timer_user_start(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + snd_timer_stop(tu->timeri); + tu->timeri->lost = 0; + tu->last_resolution = 0; + err = snd_timer_start(tu->timeri, tu->ticks); + if (err < 0) + return err; + return 0; +} + +static int snd_timer_user_stop(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + err = snd_timer_stop(tu->timeri); + if (err < 0) + return err; + return 0; +} + +static int snd_timer_user_continue(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + /* start timer instead of continue if it's not used before */ + if (!(tu->timeri->flags & SNDRV_TIMER_IFLG_PAUSED)) + return snd_timer_user_start(file); + tu->timeri->lost = 0; + err = snd_timer_continue(tu->timeri); + if (err < 0) + return err; + return 0; +} + +static int snd_timer_user_pause(struct file *file) +{ + int err; + struct snd_timer_user *tu; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + err = snd_timer_pause(tu->timeri); + if (err < 0) + return err; + return 0; +} + +static int snd_timer_user_tread(void __user *argp, struct snd_timer_user *tu, + unsigned int cmd, bool compat) +{ + int __user *p = argp; + int xarg, old_tread; + + if (tu->timeri) /* too late */ + return -EBUSY; + if (get_user(xarg, p)) + return -EFAULT; + + old_tread = tu->tread; + + if (!xarg) + tu->tread = TREAD_FORMAT_NONE; + else if (cmd == SNDRV_TIMER_IOCTL_TREAD64 || + (IS_ENABLED(CONFIG_64BIT) && !compat)) + tu->tread = TREAD_FORMAT_TIME64; + else + tu->tread = TREAD_FORMAT_TIME32; + + if (tu->tread != old_tread && + realloc_user_queue(tu, tu->queue_size) < 0) { + tu->tread = old_tread; + return -ENOMEM; + } + + return 0; +} + +enum { + SNDRV_TIMER_IOCTL_START_OLD = _IO('T', 0x20), + SNDRV_TIMER_IOCTL_STOP_OLD = _IO('T', 0x21), + SNDRV_TIMER_IOCTL_CONTINUE_OLD = _IO('T', 0x22), + SNDRV_TIMER_IOCTL_PAUSE_OLD = _IO('T', 0x23), +}; + +static long __snd_timer_user_ioctl(struct file *file, unsigned int cmd, + unsigned long arg, bool compat) +{ + struct snd_timer_user *tu; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + tu = file->private_data; + switch (cmd) { + case SNDRV_TIMER_IOCTL_PVERSION: + return put_user(SNDRV_TIMER_VERSION, p) ? -EFAULT : 0; + case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + return snd_timer_user_next_device(argp); + case SNDRV_TIMER_IOCTL_TREAD_OLD: + case SNDRV_TIMER_IOCTL_TREAD64: + return snd_timer_user_tread(argp, tu, cmd, compat); + case SNDRV_TIMER_IOCTL_GINFO: + return snd_timer_user_ginfo(file, argp); + case SNDRV_TIMER_IOCTL_GPARAMS: + return snd_timer_user_gparams(file, argp); + case SNDRV_TIMER_IOCTL_GSTATUS: + return snd_timer_user_gstatus(file, argp); + case SNDRV_TIMER_IOCTL_SELECT: + return snd_timer_user_tselect(file, argp); + case SNDRV_TIMER_IOCTL_INFO: + return snd_timer_user_info(file, argp); + case SNDRV_TIMER_IOCTL_PARAMS: + return snd_timer_user_params(file, argp); + case SNDRV_TIMER_IOCTL_STATUS32: + return snd_timer_user_status32(file, argp); + case SNDRV_TIMER_IOCTL_STATUS64: + return snd_timer_user_status64(file, argp); + case SNDRV_TIMER_IOCTL_START: + case SNDRV_TIMER_IOCTL_START_OLD: + return snd_timer_user_start(file); + case SNDRV_TIMER_IOCTL_STOP: + case SNDRV_TIMER_IOCTL_STOP_OLD: + return snd_timer_user_stop(file); + case SNDRV_TIMER_IOCTL_CONTINUE: + case SNDRV_TIMER_IOCTL_CONTINUE_OLD: + return snd_timer_user_continue(file); + case SNDRV_TIMER_IOCTL_PAUSE: + case SNDRV_TIMER_IOCTL_PAUSE_OLD: + return snd_timer_user_pause(file); + } + return -ENOTTY; +} + +static long snd_timer_user_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_timer_user *tu = file->private_data; + long ret; + + mutex_lock(&tu->ioctl_lock); + ret = __snd_timer_user_ioctl(file, cmd, arg, false); + mutex_unlock(&tu->ioctl_lock); + return ret; +} + +static int snd_timer_user_fasync(int fd, struct file * file, int on) +{ + struct snd_timer_user *tu; + + tu = file->private_data; + return snd_fasync_helper(fd, file, on, &tu->fasync); +} + +static ssize_t snd_timer_user_read(struct file *file, char __user *buffer, + size_t count, loff_t *offset) +{ + struct snd_timer_tread64 *tread; + struct snd_timer_tread32 tread32; + struct snd_timer_user *tu; + long result = 0, unit; + int qhead; + int err = 0; + + tu = file->private_data; + switch (tu->tread) { + case TREAD_FORMAT_TIME64: + unit = sizeof(struct snd_timer_tread64); + break; + case TREAD_FORMAT_TIME32: + unit = sizeof(struct snd_timer_tread32); + break; + case TREAD_FORMAT_NONE: + unit = sizeof(struct snd_timer_read); + break; + default: + WARN_ONCE(1, "Corrupt snd_timer_user\n"); + return -ENOTSUPP; + } + + mutex_lock(&tu->ioctl_lock); + spin_lock_irq(&tu->qlock); + while ((long)count - result >= unit) { + while (!tu->qused) { + wait_queue_entry_t wait; + + if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { + err = -EAGAIN; + goto _error; + } + + set_current_state(TASK_INTERRUPTIBLE); + init_waitqueue_entry(&wait, current); + add_wait_queue(&tu->qchange_sleep, &wait); + + spin_unlock_irq(&tu->qlock); + mutex_unlock(&tu->ioctl_lock); + schedule(); + mutex_lock(&tu->ioctl_lock); + spin_lock_irq(&tu->qlock); + + remove_wait_queue(&tu->qchange_sleep, &wait); + + if (tu->disconnected) { + err = -ENODEV; + goto _error; + } + if (signal_pending(current)) { + err = -ERESTARTSYS; + goto _error; + } + } + + qhead = tu->qhead++; + tu->qhead %= tu->queue_size; + tu->qused--; + spin_unlock_irq(&tu->qlock); + + tread = &tu->tqueue[qhead]; + + switch (tu->tread) { + case TREAD_FORMAT_TIME64: + if (copy_to_user(buffer, tread, + sizeof(struct snd_timer_tread64))) + err = -EFAULT; + break; + case TREAD_FORMAT_TIME32: + memset(&tread32, 0, sizeof(tread32)); + tread32 = (struct snd_timer_tread32) { + .event = tread->event, + .tstamp_sec = tread->tstamp_sec, + .tstamp_nsec = tread->tstamp_nsec, + .val = tread->val, + }; + + if (copy_to_user(buffer, &tread32, sizeof(tread32))) + err = -EFAULT; + break; + case TREAD_FORMAT_NONE: + if (copy_to_user(buffer, &tu->queue[qhead], + sizeof(struct snd_timer_read))) + err = -EFAULT; + break; + default: + err = -ENOTSUPP; + break; + } + + spin_lock_irq(&tu->qlock); + if (err < 0) + goto _error; + result += unit; + buffer += unit; + } + _error: + spin_unlock_irq(&tu->qlock); + mutex_unlock(&tu->ioctl_lock); + return result > 0 ? result : err; +} + +static __poll_t snd_timer_user_poll(struct file *file, poll_table * wait) +{ + __poll_t mask; + struct snd_timer_user *tu; + + tu = file->private_data; + + poll_wait(file, &tu->qchange_sleep, wait); + + mask = 0; + spin_lock_irq(&tu->qlock); + if (tu->qused) + mask |= EPOLLIN | EPOLLRDNORM; + if (tu->disconnected) + mask |= EPOLLERR; + spin_unlock_irq(&tu->qlock); + + return mask; +} + +#ifdef CONFIG_COMPAT +#include "timer_compat.c" +#else +#define snd_timer_user_ioctl_compat NULL +#endif + +static const struct file_operations snd_timer_f_ops = +{ + .owner = THIS_MODULE, + .read = snd_timer_user_read, + .open = snd_timer_user_open, + .release = snd_timer_user_release, + .llseek = no_llseek, + .poll = snd_timer_user_poll, + .unlocked_ioctl = snd_timer_user_ioctl, + .compat_ioctl = snd_timer_user_ioctl_compat, + .fasync = snd_timer_user_fasync, +}; + +/* unregister the system timer */ +static void snd_timer_free_all(void) +{ + struct snd_timer *timer, *n; + + list_for_each_entry_safe(timer, n, &snd_timer_list, device_list) + snd_timer_free(timer); +} + +static struct device *timer_dev; + +/* + * ENTRY functions + */ + +static int __init alsa_timer_init(void) +{ + int err; + + err = snd_device_alloc(&timer_dev, NULL); + if (err < 0) + return err; + dev_set_name(timer_dev, "timer"); + +#ifdef SNDRV_OSS_INFO_DEV_TIMERS + snd_oss_info_register(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1, + "system timer"); +#endif + + err = snd_timer_register_system(); + if (err < 0) { + pr_err("ALSA: unable to register system timer (%i)\n", err); + goto put_timer; + } + + err = snd_register_device(SNDRV_DEVICE_TYPE_TIMER, NULL, 0, + &snd_timer_f_ops, NULL, timer_dev); + if (err < 0) { + pr_err("ALSA: unable to register timer device (%i)\n", err); + snd_timer_free_all(); + goto put_timer; + } + + snd_timer_proc_init(); + return 0; + +put_timer: + put_device(timer_dev); + return err; +} + +static void __exit alsa_timer_exit(void) +{ + snd_unregister_device(timer_dev); + snd_timer_free_all(); + put_device(timer_dev); + snd_timer_proc_done(); +#ifdef SNDRV_OSS_INFO_DEV_TIMERS + snd_oss_info_unregister(SNDRV_OSS_INFO_DEV_TIMERS, SNDRV_CARDS - 1); +#endif +} + +module_init(alsa_timer_init) +module_exit(alsa_timer_exit) diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c new file mode 100644 index 0000000000..ee973b7b80 --- /dev/null +++ b/sound/core/timer_compat.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 32bit -> 64bit ioctl wrapper for timer API + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + */ + +/* This file included from timer.c */ + +#include <linux/compat.h> + +/* + * ILP32/LP64 has different size for 'long' type. Additionally, the size + * of storage alignment differs depending on architectures. Here, '__packed' + * qualifier is used so that the size of this structure is multiple of 4 and + * it fits to any architectures with 32 bit storage alignment. + */ +struct snd_timer_gparams32 { + struct snd_timer_id tid; + u32 period_num; + u32 period_den; + unsigned char reserved[32]; +} __packed; + +struct snd_timer_info32 { + u32 flags; + s32 card; + unsigned char id[64]; + unsigned char name[80]; + u32 reserved0; + u32 resolution; + unsigned char reserved[64]; +}; + +static int snd_timer_user_gparams_compat(struct file *file, + struct snd_timer_gparams32 __user *user) +{ + struct snd_timer_gparams gparams; + + if (copy_from_user(&gparams.tid, &user->tid, sizeof(gparams.tid)) || + get_user(gparams.period_num, &user->period_num) || + get_user(gparams.period_den, &user->period_den)) + return -EFAULT; + + return timer_set_gparams(&gparams); +} + +static int snd_timer_user_info_compat(struct file *file, + struct snd_timer_info32 __user *_info) +{ + struct snd_timer_user *tu; + struct snd_timer_info32 info; + struct snd_timer *t; + + tu = file->private_data; + if (!tu->timeri) + return -EBADFD; + t = tu->timeri->timer; + if (!t) + return -EBADFD; + memset(&info, 0, sizeof(info)); + info.card = t->card ? t->card->number : -1; + if (t->hw.flags & SNDRV_TIMER_HW_SLAVE) + info.flags |= SNDRV_TIMER_FLG_SLAVE; + strscpy(info.id, t->id, sizeof(info.id)); + strscpy(info.name, t->name, sizeof(info.name)); + info.resolution = t->hw.resolution; + if (copy_to_user(_info, &info, sizeof(*_info))) + return -EFAULT; + return 0; +} + +enum { + SNDRV_TIMER_IOCTL_GPARAMS32 = _IOW('T', 0x04, struct snd_timer_gparams32), + SNDRV_TIMER_IOCTL_INFO32 = _IOR('T', 0x11, struct snd_timer_info32), + SNDRV_TIMER_IOCTL_STATUS_COMPAT32 = _IOW('T', 0x14, struct snd_timer_status32), + SNDRV_TIMER_IOCTL_STATUS_COMPAT64 = _IOW('T', 0x14, struct snd_timer_status64), +}; + +static long __snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = compat_ptr(arg); + + switch (cmd) { + case SNDRV_TIMER_IOCTL_PVERSION: + case SNDRV_TIMER_IOCTL_TREAD_OLD: + case SNDRV_TIMER_IOCTL_TREAD64: + case SNDRV_TIMER_IOCTL_GINFO: + case SNDRV_TIMER_IOCTL_GSTATUS: + case SNDRV_TIMER_IOCTL_SELECT: + case SNDRV_TIMER_IOCTL_PARAMS: + case SNDRV_TIMER_IOCTL_START: + case SNDRV_TIMER_IOCTL_START_OLD: + case SNDRV_TIMER_IOCTL_STOP: + case SNDRV_TIMER_IOCTL_STOP_OLD: + case SNDRV_TIMER_IOCTL_CONTINUE: + case SNDRV_TIMER_IOCTL_CONTINUE_OLD: + case SNDRV_TIMER_IOCTL_PAUSE: + case SNDRV_TIMER_IOCTL_PAUSE_OLD: + case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + return __snd_timer_user_ioctl(file, cmd, (unsigned long)argp, true); + case SNDRV_TIMER_IOCTL_GPARAMS32: + return snd_timer_user_gparams_compat(file, argp); + case SNDRV_TIMER_IOCTL_INFO32: + return snd_timer_user_info_compat(file, argp); + case SNDRV_TIMER_IOCTL_STATUS_COMPAT32: + return snd_timer_user_status32(file, argp); + case SNDRV_TIMER_IOCTL_STATUS_COMPAT64: + return snd_timer_user_status64(file, argp); + } + return -ENOIOCTLCMD; +} + +static long snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct snd_timer_user *tu = file->private_data; + long ret; + + mutex_lock(&tu->ioctl_lock); + ret = __snd_timer_user_ioctl_compat(file, cmd, arg); + mutex_unlock(&tu->ioctl_lock); + return ret; +} diff --git a/sound/core/ump.c b/sound/core/ump.c new file mode 100644 index 0000000000..3bef1944e9 --- /dev/null +++ b/sound/core/ump.c @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal MIDI Packet (UMP) support + */ + +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/export.h> +#include <linux/mm.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/ump.h> +#include <sound/ump_convert.h> + +#define ump_err(ump, fmt, args...) dev_err((ump)->core.dev, fmt, ##args) +#define ump_warn(ump, fmt, args...) dev_warn((ump)->core.dev, fmt, ##args) +#define ump_info(ump, fmt, args...) dev_info((ump)->core.dev, fmt, ##args) +#define ump_dbg(ump, fmt, args...) dev_dbg((ump)->core.dev, fmt, ##args) + +static int snd_ump_dev_register(struct snd_rawmidi *rmidi); +static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi); +static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp); +static void snd_ump_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer); +static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream); +static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream); +static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, + int up); +static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); + +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count); +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words); +#else +static inline int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + return 0; +} +static inline void process_legacy_input(struct snd_ump_endpoint *ump, + const u32 *src, int words) +{ +} +#endif + +static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { + .dev_register = snd_ump_dev_register, + .dev_unregister = snd_ump_dev_unregister, + .ioctl = snd_ump_ioctl, + .proc_read = snd_ump_proc_read, +}; + +static const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = { + .open = snd_ump_rawmidi_open, + .close = snd_ump_rawmidi_close, + .trigger = snd_ump_rawmidi_trigger, +}; + +static const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = { + .open = snd_ump_rawmidi_open, + .close = snd_ump_rawmidi_close, + .trigger = snd_ump_rawmidi_trigger, + .drain = snd_ump_rawmidi_drain, +}; + +static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + struct snd_ump_block *fb; + + while (!list_empty(&ump->block_list)) { + fb = list_first_entry(&ump->block_list, struct snd_ump_block, + list); + list_del(&fb->list); + if (fb->private_free) + fb->private_free(fb); + kfree(fb); + } + + if (ump->private_free) + ump->private_free(ump); + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + kfree(ump->out_cvts); +#endif +} + +/** + * snd_ump_endpoint_new - create a UMP Endpoint object + * @card: the card instance + * @id: the id string for rawmidi + * @device: the device index for rawmidi + * @output: 1 for enabling output + * @input: 1 for enabling input + * @ump_ret: the pointer to store the new UMP instance + * + * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi + * instance with one input and/or one output rawmidi stream (either uni- + * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks + * that consist of one or multiple UMP Groups. + * + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself + * depending on the given @output and @input. + * + * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device + * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is + * created. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, + int output, int input, + struct snd_ump_endpoint **ump_ret) +{ + unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP; + struct snd_ump_endpoint *ump; + int err; + + if (input) + info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + if (output) + info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + if (input && output) + info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + ump = kzalloc(sizeof(*ump), GFP_KERNEL); + if (!ump) + return -ENOMEM; + INIT_LIST_HEAD(&ump->block_list); + mutex_init(&ump->open_mutex); + init_waitqueue_head(&ump->stream_wait); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + spin_lock_init(&ump->legacy_locks[0]); + spin_lock_init(&ump->legacy_locks[1]); +#endif + err = snd_rawmidi_init(&ump->core, card, id, device, + output, input, info_flags); + if (err < 0) { + snd_rawmidi_free(&ump->core); + return err; + } + + ump->info.card = card->number; + ump->info.device = device; + + ump->core.private_free = snd_ump_endpoint_free; + ump->core.ops = &snd_ump_rawmidi_ops; + if (input) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_ump_rawmidi_input_ops); + if (output) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_ump_rawmidi_output_ops); + + ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id); + *ump_ret = ump; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_endpoint_new); + +/* + * Device register / unregister hooks; + * do nothing, placeholders for avoiding the default rawmidi handling + */ + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +static void snd_ump_dev_seq_free(struct snd_seq_device *device) +{ + struct snd_ump_endpoint *ump = device->private_data; + + ump->seq_dev = NULL; +} +#endif + +static int snd_ump_dev_register(struct snd_rawmidi *rmidi) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + int err; + + err = snd_seq_device_new(ump->core.card, ump->core.device, + SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev); + if (err < 0) + return err; + ump->seq_dev->private_data = ump; + ump->seq_dev->private_free = snd_ump_dev_seq_free; + snd_device_register(ump->core.card, ump->seq_dev); +#endif + return 0; +} + +static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi) +{ + return 0; +} + +static struct snd_ump_block * +snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id) +{ + struct snd_ump_block *fb; + + list_for_each_entry(fb, &ump->block_list, list) { + if (fb->info.block_id == id) + return fb; + } + return NULL; +} + +/* + * rawmidi ops for UMP endpoint + */ +static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + int err; + + if (ump->substreams[dir]) + return -EBUSY; + err = ump->ops->open(ump, dir); + if (err < 0) + return err; + ump->substreams[dir] = substream; + return 0; +} + +static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + + ump->substreams[dir] = NULL; + ump->ops->close(ump, dir); + return 0; +} + +static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + + ump->ops->trigger(ump, dir, up); +} + +static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + + if (ump->ops->drain) + ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); +} + +/* number of 32bit words per message type */ +static unsigned char ump_packet_words[0x10] = { + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 +}; + +/** + * snd_ump_receive_ump_val - parse the UMP packet data + * @ump: UMP endpoint + * @val: UMP packet data + * + * The data is copied onto ump->input_buf[]. + * When a full packet is completed, returns the number of words (from 1 to 4). + * OTOH, if the packet is incomplete, returns 0. + */ +int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) +{ + int words; + + if (!ump->input_pending) + ump->input_pending = ump_packet_words[ump_message_type(val)]; + + ump->input_buf[ump->input_buf_head++] = val; + ump->input_pending--; + if (!ump->input_pending) { + words = ump->input_buf_head; + ump->input_buf_head = 0; + return words; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_receive_ump_val); + +/** + * snd_ump_receive - transfer UMP packets from the device + * @ump: the UMP endpoint + * @buffer: the buffer pointer to transfer + * @count: byte size to transfer + * + * Called from the driver to submit the received UMP packets from the device + * to user-space. It's essentially a wrapper of rawmidi_receive(). + * The data to receive is in CPU-native endianness. + */ +int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream; + const u32 *p = buffer; + int n, words = count >> 2; + + while (words--) { + n = snd_ump_receive_ump_val(ump, *p++); + if (!n) + continue; + ump_handle_stream_msg(ump, ump->input_buf, n); +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops) + ump->seq_ops->input_receive(ump, ump->input_buf, n); +#endif + process_legacy_input(ump, ump->input_buf, n); + } + + substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; + if (!substream) + return 0; + return snd_rawmidi_receive(substream, (const char *)buffer, count); +} +EXPORT_SYMBOL_GPL(snd_ump_receive); + +/** + * snd_ump_transmit - transmit UMP packets + * @ump: the UMP endpoint + * @buffer: the buffer pointer to transfer + * @count: byte size to transfer + * + * Called from the driver to obtain the UMP packets from user-space to the + * device. It's essentially a wrapper of rawmidi_transmit(). + * The data to transmit is in CPU-native endianness. + */ +int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream = + ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + int err; + + if (!substream) + return -ENODEV; + err = snd_rawmidi_transmit(substream, (char *)buffer, count); + /* received either data or an error? */ + if (err) + return err; + return process_legacy_output(ump, buffer, count); +} +EXPORT_SYMBOL_GPL(snd_ump_transmit); + +/** + * snd_ump_block_new - Create a UMP block + * @ump: UMP object + * @blk: block ID number to create + * @direction: direction (in/out/bidirection) + * @first_group: the first group ID (0-based) + * @num_groups: the number of groups in this block + * @blk_ret: the pointer to store the resultant block object + */ +int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, + unsigned int direction, unsigned int first_group, + unsigned int num_groups, struct snd_ump_block **blk_ret) +{ + struct snd_ump_block *fb, *p; + + if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS) + return -EINVAL; + + if (snd_ump_get_block(ump, blk)) + return -EBUSY; + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (!fb) + return -ENOMEM; + + fb->ump = ump; + fb->info.card = ump->info.card; + fb->info.device = ump->info.device; + fb->info.block_id = blk; + if (blk >= ump->info.num_blocks) + ump->info.num_blocks = blk + 1; + fb->info.direction = direction; + fb->info.active = 1; + fb->info.first_group = first_group; + fb->info.num_groups = num_groups; + /* fill the default name, may be overwritten to a better name */ + snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d", + first_group + 1, first_group + num_groups); + + /* put the entry in the ordered list */ + list_for_each_entry(p, &ump->block_list, list) { + if (p->info.block_id > blk) { + list_add_tail(&fb->list, &p->list); + goto added; + } + } + list_add_tail(&fb->list, &ump->block_list); + + added: + ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name); + *blk_ret = fb; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_block_new); + +static int snd_ump_ioctl_block(struct snd_ump_endpoint *ump, + struct snd_ump_block_info __user *argp) +{ + struct snd_ump_block *fb; + unsigned char id; + + if (get_user(id, &argp->block_id)) + return -EFAULT; + fb = snd_ump_get_block(ump, id); + if (!fb) + return -ENOENT; + if (copy_to_user(argp, &fb->info, sizeof(fb->info))) + return -EFAULT; + return 0; +} + +/* + * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl() + */ +static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + + switch (cmd) { + case SNDRV_UMP_IOCTL_ENDPOINT_INFO: + if (copy_to_user(argp, &ump->info, sizeof(ump->info))) + return -EFAULT; + return 0; + case SNDRV_UMP_IOCTL_BLOCK_INFO: + return snd_ump_ioctl_block(ump, argp); + default: + ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd); + return -ENOTTY; + } +} + +static const char *ump_direction_string(int dir) +{ + switch (dir) { + case SNDRV_UMP_DIR_INPUT: + return "input"; + case SNDRV_UMP_DIR_OUTPUT: + return "output"; + case SNDRV_UMP_DIR_BIDIRECTION: + return "bidirection"; + default: + return "unknown"; + } +} + +static const char *ump_ui_hint_string(int dir) +{ + switch (dir) { + case SNDRV_UMP_BLOCK_UI_HINT_RECEIVER: + return "receiver"; + case SNDRV_UMP_BLOCK_UI_HINT_SENDER: + return "sender"; + case SNDRV_UMP_BLOCK_UI_HINT_BOTH: + return "both"; + default: + return "unknown"; + } +} + +/* Additional proc file output */ +static void snd_ump_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_rawmidi *rmidi = entry->private_data; + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + struct snd_ump_block *fb; + + snd_iprintf(buffer, "EP Name: %s\n", ump->info.name); + snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id); + snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version); + snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps); + snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol); + if (ump->info.version) { + snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n", + ump->info.manufacturer_id); + snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id); + snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id); + snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n", + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + } + snd_iprintf(buffer, "Static Blocks: %s\n", + (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No"); + snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); + + list_for_each_entry(fb, &ump->block_list, list) { + snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id, + fb->info.name); + snd_iprintf(buffer, " Direction: %s\n", + ump_direction_string(fb->info.direction)); + snd_iprintf(buffer, " Active: %s\n", + fb->info.active ? "Yes" : "No"); + snd_iprintf(buffer, " Groups: %d-%d\n", + fb->info.first_group + 1, + fb->info.first_group + fb->info.num_groups); + snd_iprintf(buffer, " Is MIDI1: %s%s\n", + (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No", + (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : ""); + if (ump->info.version) { + snd_iprintf(buffer, " MIDI-CI Version: %d\n", + fb->info.midi_ci_version); + snd_iprintf(buffer, " Sysex8 Streams: %d\n", + fb->info.sysex8_streams); + snd_iprintf(buffer, " UI Hint: %s\n", + ump_ui_hint_string(fb->info.ui_hint)); + } + snd_iprintf(buffer, "\n"); + } +} + +/* + * UMP endpoint and function block handling + */ + +/* open / close UMP streams for the internal stream msg communication */ +static int ump_request_open(struct snd_ump_endpoint *ump) +{ + return snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT, + &ump->stream_rfile); +} + +static void ump_request_close(struct snd_ump_endpoint *ump) +{ + snd_rawmidi_kernel_release(&ump->stream_rfile); +} + +/* request a command and wait for the given response; + * @req1 and @req2 are u32 commands + * @reply is the expected UMP stream status + */ +static int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2, + u32 reply) +{ + u32 buf[4]; + + ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n", + __func__, req1, req2, reply); + memset(buf, 0, sizeof(buf)); + buf[0] = req1; + buf[1] = req2; + ump->stream_finished = 0; + ump->stream_wait_for = reply; + snd_rawmidi_kernel_write(ump->stream_rfile.output, + (unsigned char *)&buf, 16); + wait_event_timeout(ump->stream_wait, ump->stream_finished, + msecs_to_jiffies(500)); + if (!READ_ONCE(ump->stream_finished)) { + ump_dbg(ump, "%s: request timed out\n", __func__); + return -ETIMEDOUT; + } + ump->stream_finished = 0; + ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + return 0; +} + +/* append the received letters via UMP packet to the given string buffer; + * return 1 if the full string is received or 0 to continue + */ +static int ump_append_string(struct snd_ump_endpoint *ump, char *dest, + int maxsize, const u32 *buf, int offset) +{ + unsigned char format; + int c; + + format = ump_stream_message_format(buf[0]); + if (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_START) { + c = 0; + } else { + c = strlen(dest); + if (c >= maxsize - 1) + return 1; + } + + for (; offset < 16; offset++) { + dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8; + if (!dest[c]) + break; + if (++c >= maxsize - 1) + break; + } + dest[c] = 0; + return (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_END); +} + +/* handle EP info stream message; update the UMP attributes */ +static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.version = (buf->ep_info.ump_version_major << 8) | + buf->ep_info.ump_version_minor; + ump->info.num_blocks = buf->ep_info.num_function_blocks; + if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) { + ump_info(ump, "Invalid function blocks %d, fallback to 1\n", + ump->info.num_blocks); + ump->info.num_blocks = 1; + } + + if (buf->ep_info.static_function_block) + ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; + + ump->info.protocol_caps = (buf->ep_info.protocol << 8) | + buf->ep_info.jrts; + + ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n", + ump->info.version, ump->info.num_blocks, ump->info.protocol_caps); + return 1; /* finished */ +} + +/* handle EP device info stream message; update the UMP attributes */ +static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f; + ump->info.family_id = (buf->device_info.family_msb << 8) | + buf->device_info.family_lsb; + ump->info.model_id = (buf->device_info.model_msb << 8) | + buf->device_info.model_lsb; + ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f; + ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f; + ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f; + ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f; + ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n", + ump->info.manufacturer_id, + ump->info.family_id, + ump->info.model_id, + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + return 1; /* finished */ +} + +/* handle EP name stream message; update the UMP name string */ +static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.name, sizeof(ump->info.name), + buf->raw, 2); +} + +/* handle EP product id stream message; update the UMP product_id string */ +static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.product_id, + sizeof(ump->info.product_id), + buf->raw, 2); +} + +/* notify the protocol change to sequencer */ +static void seq_notify_protocol(struct snd_ump_endpoint *ump) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->switch_protocol) + ump->seq_ops->switch_protocol(ump); +#endif /* CONFIG_SND_SEQUENCER */ +} + +/** + * snd_ump_switch_protocol - switch MIDI protocol + * @ump: UMP endpoint + * @protocol: protocol to switch to + * + * Returns 1 if the protocol is actually switched, 0 if unchanged + */ +int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol) +{ + protocol &= ump->info.protocol_caps; + if (protocol == ump->info.protocol) + return 0; + + ump->info.protocol = protocol; + ump_dbg(ump, "New protocol = %x (caps = %x)\n", + protocol, ump->info.protocol_caps); + seq_notify_protocol(ump); + return 1; +} +EXPORT_SYMBOL_GPL(snd_ump_switch_protocol); + +/* handle EP stream config message; update the UMP protocol */ +static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned int protocol = + (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; + + snd_ump_switch_protocol(ump, protocol); + return 1; /* finished */ +} + +/* Extract Function Block info from UMP packet */ +static void fill_fb_info(struct snd_ump_endpoint *ump, + struct snd_ump_block_info *info, + const union snd_ump_stream_msg *buf) +{ + info->direction = buf->fb_info.direction; + info->ui_hint = buf->fb_info.ui_hint; + info->first_group = buf->fb_info.first_group; + info->num_groups = buf->fb_info.num_groups; + info->flags = buf->fb_info.midi_10; + info->active = buf->fb_info.active; + info->midi_ci_version = buf->fb_info.midi_ci_version; + info->sysex8_streams = buf->fb_info.sysex8_streams; + + ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n", + info->block_id, info->direction, info->active, + info->first_group, info->num_groups, info->midi_ci_version, + info->sysex8_streams, info->flags); +} + +/* check whether the FB info gets updated by the current message */ +static bool is_fb_info_updated(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb, + const union snd_ump_stream_msg *buf) +{ + char tmpbuf[offsetof(struct snd_ump_block_info, name)]; + + if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) { + ump_info(ump, "Skipping static FB info update (blk#%d)\n", + fb->info.block_id); + return 0; + } + + memcpy(tmpbuf, &fb->info, sizeof(tmpbuf)); + fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf); + return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0; +} + +/* notify the FB info/name change to sequencer */ +static void seq_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->notify_fb_change) + ump->seq_ops->notify_fb_change(ump, fb); +#endif +} + +/* handle FB info message; update FB info if the block is present */ +static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + + blk = buf->fb_info.function_block_id; + fb = snd_ump_get_block(ump, blk); + + /* complain only if updated after parsing */ + if (!fb && ump->parsed) { + ump_info(ump, "Function Block Info Update for non-existing block %d\n", + blk); + return -ENODEV; + } + + /* When updated after the initial parse, check the FB info update */ + if (ump->parsed && !is_fb_info_updated(ump, fb, buf)) + return 1; /* no content change */ + + if (fb) { + fill_fb_info(ump, &fb->info, buf); + if (ump->parsed) + seq_notify_fb_change(ump, fb); + } + + return 1; /* finished */ +} + +/* handle FB name message; update the FB name string */ +static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + int ret; + + blk = buf->fb_name.function_block_id; + fb = snd_ump_get_block(ump, blk); + if (!fb) + return -ENODEV; + + ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name), + buf->raw, 3); + /* notify the FB name update to sequencer, too */ + if (ret > 0 && ump->parsed) + seq_notify_fb_change(ump, fb); + return ret; +} + +static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk) +{ + struct snd_ump_block *fb; + unsigned char direction, first_group, num_groups; + const union snd_ump_stream_msg *buf = + (const union snd_ump_stream_msg *)ump->input_buf; + u32 msg; + int err; + + /* query the FB info once */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get FB info for block %d\n", blk); + return err; + } + + /* the last input must be the FB info */ + if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) { + ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw); + return -EINVAL; + } + + direction = buf->fb_info.direction; + first_group = buf->fb_info.first_group; + num_groups = buf->fb_info.num_groups; + + err = snd_ump_block_new(ump, blk, direction, first_group, num_groups, + &fb); + if (err < 0) + return err; + + fill_fb_info(ump, &fb->info, buf); + + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME); + if (err) + ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk); + + return 0; +} + +/* handle stream messages, called from snd_ump_receive() */ +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size) +{ + const union snd_ump_stream_msg *msg; + unsigned int status; + int ret; + + /* UMP stream message suppressed (for gadget UMP)? */ + if (ump->no_process_stream) + return; + + BUILD_BUG_ON(sizeof(*msg) != 16); + ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n", + buf[0], buf[1], buf[2], buf[3]); + + if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM) + return; + + msg = (const union snd_ump_stream_msg *)buf; + status = ump_stream_message_status(*buf); + switch (status) { + case UMP_STREAM_MSG_STATUS_EP_INFO: + ret = ump_handle_ep_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_DEVICE_INFO: + ret = ump_handle_device_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_EP_NAME: + ret = ump_handle_ep_name_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_PRODUCT_ID: + ret = ump_handle_product_id_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_STREAM_CFG: + ret = ump_handle_stream_cfg_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_INFO: + ret = ump_handle_fb_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_NAME: + ret = ump_handle_fb_name_msg(ump, msg); + break; + default: + return; + } + + /* when the message has been processed fully, wake up */ + if (ret > 0 && ump->stream_wait_for == status) { + WRITE_ONCE(ump->stream_finished, 1); + wake_up(&ump->stream_wait); + } +} + +/** + * snd_ump_parse_endpoint - parse endpoint and create function blocks + * @ump: UMP object + * + * Returns 0 for successful parse, -ENODEV if device doesn't respond + * (or the query is unsupported), or other error code for serious errors. + */ +int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump) +{ + int blk, err; + u32 msg; + + if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) + return -ENODEV; + + err = ump_request_open(ump); + if (err < 0) { + ump_dbg(ump, "Unable to open rawmidi device: %d\n", err); + return err; + } + + /* Check Endpoint Information */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) | + 0x0101; /* UMP version 1.1 */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO, + UMP_STREAM_MSG_STATUS_EP_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get UMP EP info\n"); + goto error; + } + + /* Request Endpoint Device Info */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO, + UMP_STREAM_MSG_STATUS_DEVICE_INFO); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP device info\n"); + + /* Request Endpoint Name */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME, + UMP_STREAM_MSG_STATUS_EP_NAME); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP name string\n"); + + /* Request Endpoint Product ID */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID, + UMP_STREAM_MSG_STATUS_PRODUCT_ID); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP product ID string\n"); + + /* Get the current stream configuration */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG, + UMP_STREAM_MSG_STATUS_STREAM_CFG); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP stream config\n"); + + /* Query and create blocks from Function Blocks */ + for (blk = 0; blk < ump->info.num_blocks; blk++) { + err = create_block_from_fb_info(ump, blk); + if (err < 0) + continue; + } + + error: + ump->parsed = true; + ump_request_close(ump); + if (err == -ETIMEDOUT) + err = -ENODEV; + return err; +} +EXPORT_SYMBOL_GPL(snd_ump_parse_endpoint); + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +/* + * Legacy rawmidi support + */ +static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = ump->legacy_mapping[substream->number]; + int err; + + mutex_lock(&ump->open_mutex); + if (ump->legacy_substreams[dir][group]) { + err = -EBUSY; + goto unlock; + } + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, + &ump->legacy_out_rfile); + if (err < 0) + goto unlock; + } + ump->legacy_out_opens++; + snd_ump_convert_reset(&ump->out_cvts[group]); + } + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = substream; + spin_unlock_irq(&ump->legacy_locks[dir]); + unlock: + mutex_unlock(&ump->open_mutex); + return 0; +} + +static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = ump->legacy_mapping[substream->number]; + + mutex_lock(&ump->open_mutex); + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = NULL; + spin_unlock_irq(&ump->legacy_locks[dir]); + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) + snd_rawmidi_kernel_release(&ump->legacy_out_rfile); + } + mutex_unlock(&ump->open_mutex); + return 0; +} + +static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + + ump->ops->trigger(ump, dir, up); +} + +static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + + if (ump->ops->drain) + ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); +} + +static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi) +{ + /* dummy, just for avoiding create superfluous seq clients */ + return 0; +} + +static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, +}; + +static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, + .drain = snd_ump_legacy_drain, +}; + +static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = { + .dev_register = snd_ump_legacy_dev_register, +}; + +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream; + struct ump_cvt_to_ump *ctx; + const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT; + unsigned char c; + int group, size = 0; + unsigned long flags; + + if (!ump->out_cvts || !ump->legacy_out_opens) + return 0; + + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) { + substream = ump->legacy_substreams[dir][group]; + if (!substream) + continue; + ctx = &ump->out_cvts[group]; + while (!ctx->ump_bytes && + snd_rawmidi_transmit(substream, &c, 1) > 0) + snd_ump_convert_to_ump(ctx, group, ump->info.protocol, c); + if (ctx->ump_bytes && ctx->ump_bytes <= count) { + size = ctx->ump_bytes; + memcpy(buffer, ctx->ump, size); + ctx->ump_bytes = 0; + break; + } + } + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); + return size; +} + +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words) +{ + struct snd_rawmidi_substream *substream; + unsigned char buf[16]; + unsigned char group; + unsigned long flags; + const int dir = SNDRV_RAWMIDI_STREAM_INPUT; + int size; + + size = snd_ump_convert_from_ump(src, buf, &group); + if (size <= 0) + return; + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + substream = ump->legacy_substreams[dir][group]; + if (substream) + snd_rawmidi_receive(substream, buf, size); + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); +} + +/* Fill ump->legacy_mapping[] for groups to be used for legacy rawmidi */ +static int fill_legacy_mapping(struct snd_ump_endpoint *ump) +{ + struct snd_ump_block *fb; + unsigned int group_maps = 0; + int i, num; + + if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) { + list_for_each_entry(fb, &ump->block_list, list) { + for (i = 0; i < fb->info.num_groups; i++) + group_maps |= 1U << (fb->info.first_group + i); + } + if (!group_maps) + ump_info(ump, "No UMP Group is found in FB\n"); + } + + /* use all groups for non-static case */ + if (!group_maps) + group_maps = (1U << SNDRV_UMP_MAX_GROUPS) - 1; + + num = 0; + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) + if (group_maps & (1U << i)) + ump->legacy_mapping[num++] = i; + + return num; +} + +static void fill_substream_names(struct snd_ump_endpoint *ump, + struct snd_rawmidi *rmidi, int dir) +{ + struct snd_rawmidi_substream *s; + + list_for_each_entry(s, &rmidi->streams[dir].substreams, list) + snprintf(s->name, sizeof(s->name), "Group %d (%.16s)", + ump->legacy_mapping[s->number] + 1, ump->info.name); +} + +int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device) +{ + struct snd_rawmidi *rmidi; + bool input, output; + int err, num; + + ump->out_cvts = kcalloc(SNDRV_UMP_MAX_GROUPS, + sizeof(*ump->out_cvts), GFP_KERNEL); + if (!ump->out_cvts) + return -ENOMEM; + + num = fill_legacy_mapping(ump); + + input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT; + output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT; + err = snd_rawmidi_new(ump->core.card, id, device, + output ? num : 0, input ? num : 0, + &rmidi); + if (err < 0) { + kfree(ump->out_cvts); + return err; + } + + if (input) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_ump_legacy_input_ops); + if (output) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_ump_legacy_output_ops); + snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)", + ump->info.name); + rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP; + rmidi->ops = &snd_ump_legacy_ops; + rmidi->private_data = ump; + ump->legacy_rmidi = rmidi; + if (input) + fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT); + if (output) + fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT); + + ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id); + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi); +#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */ + +MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c new file mode 100644 index 0000000000..de04799fdb --- /dev/null +++ b/sound/core/ump_convert.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Helpers for UMP <-> MIDI 1.0 byte stream conversion + */ + +#include <linux/module.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/asound.h> +#include <sound/ump.h> +#include <sound/ump_convert.h> + +/* + * Upgrade / downgrade value bits + */ +static u8 downscale_32_to_7bit(u32 src) +{ + return src >> 25; +} + +static u16 downscale_32_to_14bit(u32 src) +{ + return src >> 18; +} + +static u8 downscale_16_to_7bit(u16 src) +{ + return src >> 9; +} + +static u16 upscale_7_to_16bit(u8 src) +{ + u16 val, repeat; + + val = (u16)src << 9; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 3) | (repeat >> 3); +} + +static u32 upscale_7_to_32bit(u8 src) +{ + u32 val, repeat; + + val = src << 25; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 19) | (repeat << 13) | + (repeat << 7) | (repeat << 1) | (repeat >> 5); +} + +static u32 upscale_14_to_32bit(u16 src) +{ + u32 val, repeat; + + val = src << 18; + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +/* + * UMP -> MIDI 1 byte stream conversion + */ +/* convert a UMP System message to MIDI 1.0 byte stream */ +static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + switch (ump_message_status_code(data)) { + case UMP_SYSTEM_STATUS_MIDI_TIME_CODE: + case UMP_SYSTEM_STATUS_SONG_SELECT: + buf[1] = (data >> 8) & 0x7f; + return 2; + case UMP_SYSTEM_STATUS_SONG_POSITION: + buf[1] = (data >> 8) & 0x7f; + buf[2] = data & 0x7f; + return 3; + default: + return 1; + } +} + +/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + buf[1] = (data >> 8) & 0xff; + switch (ump_message_status_code(data)) { + case UMP_MSG_STATUS_PROGRAM: + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + return 2; + default: + buf[2] = data & 0xff; + return 3; + } +} + +/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2, + unsigned char *buf) +{ + unsigned char status = midi2->note.status; + unsigned char channel = midi2->note.channel; + u16 v; + + buf[0] = (status << 4) | channel; + switch (status) { + case UMP_MSG_STATUS_NOTE_OFF: + case UMP_MSG_STATUS_NOTE_ON: + buf[1] = midi2->note.note; + buf[2] = downscale_16_to_7bit(midi2->note.velocity); + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + buf[2] = 1; + return 3; + case UMP_MSG_STATUS_POLY_PRESSURE: + buf[1] = midi2->paf.note; + buf[2] = downscale_32_to_7bit(midi2->paf.data); + return 3; + case UMP_MSG_STATUS_CC: + buf[1] = midi2->cc.index; + buf[2] = downscale_32_to_7bit(midi2->cc.data); + return 3; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + buf[1] = downscale_32_to_7bit(midi2->caf.data); + return 2; + case UMP_MSG_STATUS_PROGRAM: + if (midi2->pg.bank_valid) { + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = UMP_CC_BANK_SELECT; + buf[2] = midi2->pg.bank_msb; + buf[3] = channel | (UMP_MSG_STATUS_CC << 4); + buf[4] = UMP_CC_BANK_SELECT_LSB; + buf[5] = midi2->pg.bank_lsb; + buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4); + buf[7] = midi2->pg.program; + return 8; + } + buf[1] = midi2->pg.program; + return 2; + case UMP_MSG_STATUS_PITCH_BEND: + v = downscale_32_to_14bit(midi2->pb.data); + buf[1] = v & 0x7f; + buf[2] = v >> 7; + return 3; + case UMP_MSG_STATUS_RPN: + case UMP_MSG_STATUS_NRPN: + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB; + buf[2] = midi2->rpn.bank; + buf[3] = buf[0]; + buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB; + buf[5] = midi2->rpn.index; + buf[6] = buf[0]; + buf[7] = UMP_CC_DATA; + v = downscale_32_to_14bit(midi2->rpn.data); + buf[8] = v >> 7; + buf[9] = buf[0]; + buf[10] = UMP_CC_DATA_LSB; + buf[11] = v & 0x7f; + return 12; + default: + return 0; + } +} + +/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */ +static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf) +{ + unsigned char status; + unsigned char bytes; + int size, offset; + + status = ump_sysex_message_status(*data); + if (status > UMP_SYSEX_STATUS_END) + return 0; // unsupported, skip + bytes = ump_sysex_message_length(*data); + if (bytes > 6) + return 0; // skip + + size = 0; + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_START) { + buf[0] = UMP_MIDI1_MSG_SYSEX_START; + size = 1; + } + + offset = 8; + for (; bytes; bytes--, size++) { + buf[size] = (*data >> offset) & 0x7f; + if (!offset) { + offset = 24; + data++; + } else { + offset -= 8; + } + } + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_END) + buf[size++] = UMP_MIDI1_MSG_SYSEX_END; + + return size; +} + +/** + * snd_ump_convert_from_ump - convert from UMP to legacy MIDI + * @data: UMP packet + * @buf: buffer to store legacy MIDI data + * @group_ret: pointer to store the target group + * + * Convert from a UMP packet @data to MIDI 1.0 bytes at @buf. + * The target group is stored at @group_ret. + * + * The function returns the number of bytes of MIDI 1.0 stream. + */ +int snd_ump_convert_from_ump(const u32 *data, + unsigned char *buf, + unsigned char *group_ret) +{ + *group_ret = ump_message_group(*data); + + switch (ump_message_type(*data)) { + case UMP_MSG_TYPE_SYSTEM: + return cvt_ump_system_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE: + return cvt_ump_midi1_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE: + return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data, + buf); + case UMP_MSG_TYPE_DATA: + return cvt_ump_sysex7_to_legacy(data, buf); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_convert_from_ump); + +/* + * MIDI 1 byte stream -> UMP conversion + */ +/* convert MIDI 1.0 SysEx to a UMP packet */ +static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data, bool finish) +{ + unsigned char status; + bool start = cvt->in_sysex == 1; + int i, offset; + + if (start && finish) + status = UMP_SYSEX_STATUS_SINGLE; + else if (start) + status = UMP_SYSEX_STATUS_START; + else if (finish) + status = UMP_SYSEX_STATUS_END; + else + status = UMP_SYSEX_STATUS_CONTINUE; + *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len); + offset = 8; + for (i = 0; i < cvt->len; i++) { + *data |= cvt->buf[i] << offset; + if (!offset) { + offset = 24; + data++; + } else + offset -= 8; + } + cvt->len = 0; + if (finish) + cvt->in_sysex = 0; + else + cvt->in_sysex++; + return 8; +} + +/* convert to a UMP System message */ +static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data) +{ + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]); + if (cvt->cmd_bytes > 1) + data[0] |= cvt->buf[1] << 8; + if (cvt->cmd_bytes > 2) + data[0] |= cvt->buf[2]; + return 4; +} + +static void fill_rpn(struct ump_cvt_to_ump_bank *cc, + union snd_ump_midi2_msg *midi2) +{ + if (cc->rpn_set) { + midi2->rpn.status = UMP_MSG_STATUS_RPN; + midi2->rpn.bank = cc->cc_rpn_msb; + midi2->rpn.index = cc->cc_rpn_lsb; + cc->rpn_set = 0; + cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; + } else { + midi2->rpn.status = UMP_MSG_STATUS_NRPN; + midi2->rpn.bank = cc->cc_nrpn_msb; + midi2->rpn.index = cc->cc_nrpn_lsb; + cc->nrpn_set = 0; + cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0; + } + midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | + cc->cc_data_lsb); + cc->cc_data_msb = cc->cc_data_lsb = 0; +} + +/* convert to a MIDI 1.0 Channel Voice message */ +static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, + unsigned int protocol, + u32 *data, unsigned char bytes) +{ + const unsigned char *buf = cvt->buf; + struct ump_cvt_to_ump_bank *cc; + union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data; + unsigned char status, channel; + + BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4); + BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8); + + /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */ + if (protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { + data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE, + group, 0, buf[0]); + data[0] |= buf[1] << 8; + if (bytes > 2) + data[0] |= buf[2]; + return 4; + } + + status = *buf >> 4; + channel = *buf & 0x0f; + cc = &cvt->bank[channel]; + + /* special handling: treat note-on with 0 velocity as note-off */ + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + status = UMP_MSG_STATUS_NOTE_OFF; + + /* initialize the packet */ + data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE, + group, status, channel); + data[1] = 0; + + switch (status) { + case UMP_MSG_STATUS_NOTE_ON: + case UMP_MSG_STATUS_NOTE_OFF: + midi2->note.note = buf[1]; + midi2->note.velocity = upscale_7_to_16bit(buf[2]); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi2->paf.note = buf[1]; + midi2->paf.data = upscale_7_to_32bit(buf[2]); + break; + case UMP_MSG_STATUS_CC: + switch (buf[1]) { + case UMP_CC_RPN_MSB: + cc->rpn_set = 1; + cc->cc_rpn_msb = buf[2]; + return 0; // skip + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->nrpn_set = 1; + cc->cc_nrpn_msb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT: + cc->bank_set = 1; + cc->cc_bank_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = buf[2]; + if (cc->rpn_set || cc->nrpn_set) + fill_rpn(cc, midi2); + else + return 0; // skip + break; + default: + midi2->cc.index = buf[1]; + midi2->cc.data = upscale_7_to_32bit(buf[2]); + break; + } + break; + case UMP_MSG_STATUS_PROGRAM: + midi2->pg.program = buf[1]; + if (cc->bank_set) { + midi2->pg.bank_valid = 1; + midi2->pg.bank_msb = cc->cc_bank_msb; + midi2->pg.bank_lsb = cc->cc_bank_lsb; + cc->bank_set = 0; + cc->cc_bank_msb = cc->cc_bank_lsb = 0; + } + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi2->caf.data = upscale_7_to_32bit(buf[1]); + break; + case UMP_MSG_STATUS_PITCH_BEND: + midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7)); + break; + default: + return 0; + } + + return 8; +} + +static int do_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c, u32 *data) +{ + /* bytes for 0x80-0xf0 */ + static unsigned char cmd_bytes[8] = { + 3, 3, 3, 3, 2, 2, 3, 0 + }; + /* bytes for 0xf0-0xff */ + static unsigned char system_bytes[16] = { + 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 + }; + unsigned char bytes; + + if (c == UMP_MIDI1_MSG_SYSEX_START) { + cvt->in_sysex = 1; + cvt->len = 0; + return 0; + } + if (c == UMP_MIDI1_MSG_SYSEX_END) { + if (!cvt->in_sysex) + return 0; /* skip */ + return cvt_legacy_sysex_to_ump(cvt, group, data, true); + } + + if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) { + bytes = system_bytes[c & 0x0f]; + if (!bytes) + return 0; /* skip */ + if (bytes == 1) { + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c); + return 4; + } + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (c & 0x80) { + bytes = cmd_bytes[(c >> 4) & 7]; + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (cvt->in_sysex) { + cvt->buf[cvt->len++] = c; + if (cvt->len == 6) + return cvt_legacy_sysex_to_ump(cvt, group, data, false); + return 0; + } + + if (!cvt->len) + return 0; + + cvt->buf[cvt->len++] = c; + if (cvt->len < cvt->cmd_bytes) + return 0; + cvt->len = 1; + if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME) + return cvt_legacy_system_to_ump(cvt, group, data); + return cvt_legacy_cmd_to_ump(cvt, group, protocol, data, cvt->cmd_bytes); +} + +/** + * snd_ump_convert_to_ump - convert legacy MIDI byte to UMP packet + * @cvt: converter context + * @group: target UMP group + * @protocol: target UMP protocol + * @c: MIDI 1.0 byte data + * + * Feed a MIDI 1.0 byte @c and convert to a UMP packet if completed. + * The result is stored in the buffer in @cvt. + */ +void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c) +{ + cvt->ump_bytes = do_convert_to_ump(cvt, group, protocol, c, cvt->ump); +} +EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump); diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c new file mode 100644 index 0000000000..378d2c7c3d --- /dev/null +++ b/sound/core/vmaster.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Virtual master and follower controls + * + * Copyright (c) 2008 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/slab.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +/* + * a subset of information returned via ctl info callback + */ +struct link_ctl_info { + snd_ctl_elem_type_t type; /* value type */ + int count; /* item count */ + int min_val, max_val; /* min, max values */ +}; + +/* + * link master - this contains a list of follower controls that are + * identical types, i.e. info returns the same value type and value + * ranges, but may have different number of counts. + * + * The master control is so far only mono volume/switch for simplicity. + * The same value will be applied to all followers. + */ +struct link_master { + struct list_head followers; + struct link_ctl_info info; + int val; /* the master value */ + unsigned int tlv[4]; + void (*hook)(void *private_data, int); + void *hook_private_data; +}; + +/* + * link follower - this contains a follower control element + * + * It fakes the control callbacks with additional attenuation by the + * master control. A follower may have either one or two channels. + */ + +struct link_follower { + struct list_head list; + struct link_master *master; + struct link_ctl_info info; + int vals[2]; /* current values */ + unsigned int flags; + struct snd_kcontrol *kctl; /* original kcontrol pointer */ + struct snd_kcontrol follower; /* the copy of original control entry */ +}; + +static int follower_update(struct link_follower *follower) +{ + struct snd_ctl_elem_value *uctl; + int err, ch; + + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (!uctl) + return -ENOMEM; + uctl->id = follower->follower.id; + err = follower->follower.get(&follower->follower, uctl); + if (err < 0) + goto error; + for (ch = 0; ch < follower->info.count; ch++) + follower->vals[ch] = uctl->value.integer.value[ch]; + error: + kfree(uctl); + return err < 0 ? err : 0; +} + +/* get the follower ctl info and save the initial values */ +static int follower_init(struct link_follower *follower) +{ + struct snd_ctl_elem_info *uinfo; + int err; + + if (follower->info.count) { + /* already initialized */ + if (follower->flags & SND_CTL_FOLLOWER_NEED_UPDATE) + return follower_update(follower); + return 0; + } + + uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL); + if (!uinfo) + return -ENOMEM; + uinfo->id = follower->follower.id; + err = follower->follower.info(&follower->follower, uinfo); + if (err < 0) { + kfree(uinfo); + return err; + } + follower->info.type = uinfo->type; + follower->info.count = uinfo->count; + if (follower->info.count > 2 || + (follower->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER && + follower->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) { + pr_err("ALSA: vmaster: invalid follower element\n"); + kfree(uinfo); + return -EINVAL; + } + follower->info.min_val = uinfo->value.integer.min; + follower->info.max_val = uinfo->value.integer.max; + kfree(uinfo); + + return follower_update(follower); +} + +/* initialize master volume */ +static int master_init(struct link_master *master) +{ + struct link_follower *follower; + + if (master->info.count) + return 0; /* already initialized */ + + list_for_each_entry(follower, &master->followers, list) { + int err = follower_init(follower); + if (err < 0) + return err; + master->info = follower->info; + master->info.count = 1; /* always mono */ + /* set full volume as default (= no attenuation) */ + master->val = master->info.max_val; + if (master->hook) + master->hook(master->hook_private_data, master->val); + return 1; + } + return -ENOENT; +} + +static int follower_get_val(struct link_follower *follower, + struct snd_ctl_elem_value *ucontrol) +{ + int err, ch; + + err = follower_init(follower); + if (err < 0) + return err; + for (ch = 0; ch < follower->info.count; ch++) + ucontrol->value.integer.value[ch] = follower->vals[ch]; + return 0; +} + +static int follower_put_val(struct link_follower *follower, + struct snd_ctl_elem_value *ucontrol) +{ + int err, ch, vol; + + err = master_init(follower->master); + if (err < 0) + return err; + + switch (follower->info.type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + for (ch = 0; ch < follower->info.count; ch++) + ucontrol->value.integer.value[ch] &= + !!follower->master->val; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + for (ch = 0; ch < follower->info.count; ch++) { + /* max master volume is supposed to be 0 dB */ + vol = ucontrol->value.integer.value[ch]; + vol += follower->master->val - follower->master->info.max_val; + if (vol < follower->info.min_val) + vol = follower->info.min_val; + else if (vol > follower->info.max_val) + vol = follower->info.max_val; + ucontrol->value.integer.value[ch] = vol; + } + break; + } + return follower->follower.put(&follower->follower, ucontrol); +} + +/* + * ctl callbacks for followers + */ +static int follower_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + return follower->follower.info(&follower->follower, uinfo); +} + +static int follower_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + return follower_get_val(follower, ucontrol); +} + +static int follower_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + int err, ch, changed = 0; + + err = follower_init(follower); + if (err < 0) + return err; + for (ch = 0; ch < follower->info.count; ch++) { + if (follower->vals[ch] != ucontrol->value.integer.value[ch]) { + changed = 1; + follower->vals[ch] = ucontrol->value.integer.value[ch]; + } + } + if (!changed) + return 0; + err = follower_put_val(follower, ucontrol); + if (err < 0) + return err; + return 1; +} + +static int follower_tlv_cmd(struct snd_kcontrol *kcontrol, + int op_flag, unsigned int size, + unsigned int __user *tlv) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + /* FIXME: this assumes that the max volume is 0 dB */ + return follower->follower.tlv.c(&follower->follower, op_flag, size, tlv); +} + +static void follower_free(struct snd_kcontrol *kcontrol) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + if (follower->follower.private_free) + follower->follower.private_free(&follower->follower); + if (follower->master) + list_del(&follower->list); + kfree(follower); +} + +/* + * Add a follower control to the group with the given master control + * + * All followers must be the same type (returning the same information + * via info callback). The function doesn't check it, so it's your + * responsibility. + * + * Also, some additional limitations: + * - at most two channels + * - logarithmic volume control (dB level), no linear volume + * - master can only attenuate the volume, no gain + */ +int _snd_ctl_add_follower(struct snd_kcontrol *master, + struct snd_kcontrol *follower, + unsigned int flags) +{ + struct link_master *master_link = snd_kcontrol_chip(master); + struct link_follower *srec; + + srec = kzalloc(struct_size(srec, follower.vd, follower->count), + GFP_KERNEL); + if (!srec) + return -ENOMEM; + srec->kctl = follower; + srec->follower = *follower; + memcpy(srec->follower.vd, follower->vd, follower->count * sizeof(*follower->vd)); + srec->master = master_link; + srec->flags = flags; + + /* override callbacks */ + follower->info = follower_info; + follower->get = follower_get; + follower->put = follower_put; + if (follower->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) + follower->tlv.c = follower_tlv_cmd; + follower->private_data = srec; + follower->private_free = follower_free; + + list_add_tail(&srec->list, &master_link->followers); + return 0; +} +EXPORT_SYMBOL(_snd_ctl_add_follower); + +/** + * snd_ctl_add_followers - add multiple followers to vmaster + * @card: card instance + * @master: the target vmaster kcontrol object + * @list: NULL-terminated list of name strings of followers to be added + * + * Adds the multiple follower kcontrols with the given names. + * Returns 0 for success or a negative error code. + */ +int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master, + const char * const *list) +{ + struct snd_kcontrol *follower; + int err; + + for (; *list; list++) { + follower = snd_ctl_find_id_mixer(card, *list); + if (follower) { + err = snd_ctl_add_follower(master, follower); + if (err < 0) + return err; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_ctl_add_followers); + +/* + * ctl callbacks for master controls + */ +static int master_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int ret; + + ret = master_init(master); + if (ret < 0) + return ret; + uinfo->type = master->info.type; + uinfo->count = master->info.count; + uinfo->value.integer.min = master->info.min_val; + uinfo->value.integer.max = master->info.max_val; + return 0; +} + +static int master_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int err = master_init(master); + if (err < 0) + return err; + ucontrol->value.integer.value[0] = master->val; + return 0; +} + +static int sync_followers(struct link_master *master, int old_val, int new_val) +{ + struct link_follower *follower; + struct snd_ctl_elem_value *uval; + + uval = kmalloc(sizeof(*uval), GFP_KERNEL); + if (!uval) + return -ENOMEM; + list_for_each_entry(follower, &master->followers, list) { + master->val = old_val; + uval->id = follower->follower.id; + follower_get_val(follower, uval); + master->val = new_val; + follower_put_val(follower, uval); + } + kfree(uval); + return 0; +} + +static int master_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int err, new_val, old_val; + bool first_init; + + err = master_init(master); + if (err < 0) + return err; + first_init = err; + old_val = master->val; + new_val = ucontrol->value.integer.value[0]; + if (new_val == old_val) + return 0; + + err = sync_followers(master, old_val, new_val); + if (err < 0) + return err; + if (master->hook && !first_init) + master->hook(master->hook_private_data, master->val); + return 1; +} + +static void master_free(struct snd_kcontrol *kcontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + struct link_follower *follower, *n; + + /* free all follower links and retore the original follower kctls */ + list_for_each_entry_safe(follower, n, &master->followers, list) { + struct snd_kcontrol *sctl = follower->kctl; + struct list_head olist = sctl->list; + memcpy(sctl, &follower->follower, sizeof(*sctl)); + memcpy(sctl->vd, follower->follower.vd, + sctl->count * sizeof(*sctl->vd)); + sctl->list = olist; /* keep the current linked-list */ + kfree(follower); + } + kfree(master); +} + + +/** + * snd_ctl_make_virtual_master - Create a virtual master control + * @name: name string of the control element to create + * @tlv: optional TLV int array for dB information + * + * Creates a virtual master control with the given name string. + * + * After creating a vmaster element, you can add the follower controls + * via snd_ctl_add_follower() or snd_ctl_add_follower_uncached(). + * + * The optional argument @tlv can be used to specify the TLV information + * for dB scale of the master control. It should be a single element + * with #SNDRV_CTL_TLVT_DB_SCALE, #SNDRV_CTL_TLV_DB_MINMAX or + * #SNDRV_CTL_TLVT_DB_MINMAX_MUTE type, and should be the max 0dB. + * + * Return: The created control element, or %NULL for errors (ENOMEM). + */ +struct snd_kcontrol *snd_ctl_make_virtual_master(char *name, + const unsigned int *tlv) +{ + struct link_master *master; + struct snd_kcontrol *kctl; + struct snd_kcontrol_new knew; + + memset(&knew, 0, sizeof(knew)); + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.name = name; + knew.info = master_info; + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return NULL; + INIT_LIST_HEAD(&master->followers); + + kctl = snd_ctl_new1(&knew, master); + if (!kctl) { + kfree(master); + return NULL; + } + /* override some callbacks */ + kctl->info = master_info; + kctl->get = master_get; + kctl->put = master_put; + kctl->private_free = master_free; + + /* additional (constant) TLV read */ + if (tlv) { + unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE]; + if (type == SNDRV_CTL_TLVT_DB_SCALE || + type == SNDRV_CTL_TLVT_DB_MINMAX || + type == SNDRV_CTL_TLVT_DB_MINMAX_MUTE) { + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + memcpy(master->tlv, tlv, sizeof(master->tlv)); + kctl->tlv.p = master->tlv; + } + } + + return kctl; +} +EXPORT_SYMBOL(snd_ctl_make_virtual_master); + +/** + * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control + * @kcontrol: vmaster kctl element + * @hook: the hook function + * @private_data: the private_data pointer to be saved + * + * Adds the given hook to the vmaster control element so that it's called + * at each time when the value is changed. + * + * Return: Zero. + */ +int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, + void (*hook)(void *private_data, int), + void *private_data) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + master->hook = hook; + master->hook_private_data = private_data; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ctl_add_vmaster_hook); + +/** + * snd_ctl_sync_vmaster - Sync the vmaster followers and hook + * @kcontrol: vmaster kctl element + * @hook_only: sync only the hook + * + * Forcibly call the put callback of each follower and call the hook function + * to synchronize with the current value of the given vmaster element. + * NOP when NULL is passed to @kcontrol. + */ +void snd_ctl_sync_vmaster(struct snd_kcontrol *kcontrol, bool hook_only) +{ + struct link_master *master; + bool first_init = false; + + if (!kcontrol) + return; + master = snd_kcontrol_chip(kcontrol); + if (!hook_only) { + int err = master_init(master); + if (err < 0) + return; + first_init = err; + err = sync_followers(master, master->val, master->val); + if (err < 0) + return; + } + + if (master->hook && !first_init) + master->hook(master->hook_private_data, master->val); +} +EXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster); + +/** + * snd_ctl_apply_vmaster_followers - Apply function to each vmaster follower + * @kctl: vmaster kctl element + * @func: function to apply + * @arg: optional function argument + * + * Apply the function @func to each follower kctl of the given vmaster kctl. + * + * Return: 0 if successful, or a negative error code + */ +int snd_ctl_apply_vmaster_followers(struct snd_kcontrol *kctl, + int (*func)(struct snd_kcontrol *vfollower, + struct snd_kcontrol *follower, + void *arg), + void *arg) +{ + struct link_master *master; + struct link_follower *follower; + int err; + + master = snd_kcontrol_chip(kctl); + err = master_init(master); + if (err < 0) + return err; + list_for_each_entry(follower, &master->followers, list) { + err = func(follower->kctl, &follower->follower, arg); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_ctl_apply_vmaster_followers); |