summaryrefslogtreecommitdiffstats
path: root/sound/core
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /sound/core
parentInitial commit. (diff)
downloadlinux-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')
-rw-r--r--sound/core/Kconfig250
-rw-r--r--sound/core/Makefile55
-rw-r--r--sound/core/compress_offload.c1209
-rw-r--r--sound/core/control.c2562
-rw-r--r--sound/core/control_compat.c483
-rw-r--r--sound/core/control_led.c794
-rw-r--r--sound/core/ctljack.c84
-rw-r--r--sound/core/device.c262
-rw-r--r--sound/core/hrtimer.c169
-rw-r--r--sound/core/hwdep.c549
-rw-r--r--sound/core/hwdep_compat.c55
-rw-r--r--sound/core/info.c920
-rw-r--r--sound/core/info_oss.c106
-rw-r--r--sound/core/init.c1177
-rw-r--r--sound/core/isadma.c138
-rw-r--r--sound/core/jack.c697
-rw-r--r--sound/core/memalloc.c950
-rw-r--r--sound/core/memalloc_local.h16
-rw-r--r--sound/core/memory.c115
-rw-r--r--sound/core/misc.c241
-rw-r--r--sound/core/oss/Makefile14
-rw-r--r--sound/core/oss/copy.c92
-rw-r--r--sound/core/oss/io.c141
-rw-r--r--sound/core/oss/linear.c180
-rw-r--r--sound/core/oss/mixer_oss.c1460
-rw-r--r--sound/core/oss/mulaw.c346
-rw-r--r--sound/core/oss/pcm_oss.c3244
-rw-r--r--sound/core/oss/pcm_plugin.c781
-rw-r--r--sound/core/oss/pcm_plugin.h168
-rw-r--r--sound/core/oss/rate.c348
-rw-r--r--sound/core/oss/route.c111
-rw-r--r--sound/core/pcm.c1249
-rw-r--r--sound/core/pcm_compat.c656
-rw-r--r--sound/core/pcm_dmaengine.c473
-rw-r--r--sound/core/pcm_drm_eld.c163
-rw-r--r--sound/core/pcm_iec958.c213
-rw-r--r--sound/core/pcm_lib.c2567
-rw-r--r--sound/core/pcm_local.h83
-rw-r--r--sound/core/pcm_memory.c563
-rw-r--r--sound/core/pcm_misc.c616
-rw-r--r--sound/core/pcm_native.c4151
-rw-r--r--sound/core/pcm_param_trace.h143
-rw-r--r--sound/core/pcm_timer.c130
-rw-r--r--sound/core/pcm_trace.h149
-rw-r--r--sound/core/rawmidi.c2202
-rw-r--r--sound/core/rawmidi_compat.c127
-rw-r--r--sound/core/seq/Kconfig77
-rw-r--r--sound/core/seq/Makefile27
-rw-r--r--sound/core/seq/oss/Makefile11
-rw-r--r--sound/core/seq/oss/seq_oss.c311
-rw-r--r--sound/core/seq/oss/seq_oss_device.h168
-rw-r--r--sound/core/seq/oss/seq_oss_event.c447
-rw-r--r--sound/core/seq/oss/seq_oss_event.h99
-rw-r--r--sound/core/seq/oss/seq_oss_init.c505
-rw-r--r--sound/core/seq/oss/seq_oss_ioctl.c178
-rw-r--r--sound/core/seq/oss/seq_oss_midi.c718
-rw-r--r--sound/core/seq/oss/seq_oss_midi.h35
-rw-r--r--sound/core/seq/oss/seq_oss_readq.c250
-rw-r--r--sound/core/seq/oss/seq_oss_readq.h45
-rw-r--r--sound/core/seq/oss/seq_oss_rw.c201
-rw-r--r--sound/core/seq/oss/seq_oss_synth.c666
-rw-r--r--sound/core/seq/oss/seq_oss_synth.h39
-rw-r--r--sound/core/seq/oss/seq_oss_timer.c264
-rw-r--r--sound/core/seq/oss/seq_oss_timer.h47
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.c160
-rw-r--r--sound/core/seq/oss/seq_oss_writeq.h37
-rw-r--r--sound/core/seq/seq.c120
-rw-r--r--sound/core/seq/seq_clientmgr.c2759
-rw-r--r--sound/core/seq/seq_clientmgr.h116
-rw-r--r--sound/core/seq/seq_compat.c125
-rw-r--r--sound/core/seq/seq_dummy.c222
-rw-r--r--sound/core/seq/seq_fifo.c283
-rw-r--r--sound/core/seq/seq_fifo.h59
-rw-r--r--sound/core/seq/seq_info.c60
-rw-r--r--sound/core/seq/seq_info.h25
-rw-r--r--sound/core/seq/seq_lock.c26
-rw-r--r--sound/core/seq/seq_lock.h22
-rw-r--r--sound/core/seq/seq_memory.c570
-rw-r--r--sound/core/seq/seq_memory.h105
-rw-r--r--sound/core/seq/seq_midi.c463
-rw-r--r--sound/core/seq/seq_midi_emul.c723
-rw-r--r--sound/core/seq/seq_midi_event.c459
-rw-r--r--sound/core/seq/seq_ports.c741
-rw-r--r--sound/core/seq/seq_ports.h146
-rw-r--r--sound/core/seq/seq_prioq.c436
-rw-r--r--sound/core/seq/seq_prioq.h45
-rw-r--r--sound/core/seq/seq_queue.c774
-rw-r--r--sound/core/seq/seq_queue.h95
-rw-r--r--sound/core/seq/seq_system.c177
-rw-r--r--sound/core/seq/seq_system.h31
-rw-r--r--sound/core/seq/seq_timer.c506
-rw-r--r--sound/core/seq/seq_timer.h134
-rw-r--r--sound/core/seq/seq_ump_client.c562
-rw-r--r--sound/core/seq/seq_ump_convert.c1208
-rw-r--r--sound/core/seq/seq_ump_convert.h22
-rw-r--r--sound/core/seq/seq_virmidi.c528
-rw-r--r--sound/core/seq_device.c310
-rw-r--r--sound/core/sound.c427
-rw-r--r--sound/core/sound_oss.c250
-rw-r--r--sound/core/timer.c2358
-rw-r--r--sound/core/timer_compat.c124
-rw-r--r--sound/core/ump.c1212
-rw-r--r--sound/core/ump_convert.c505
-rw-r--r--sound/core/vmaster.c550
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(&register_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(&register_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(&register_mutex);
+ hwdep = snd_hwdep_search(card, device);
+ if (hwdep)
+ err = snd_hwdep_info(hwdep, info);
+ else
+ err = -ENXIO;
+ mutex_unlock(&register_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(&register_mutex);
+ if (snd_hwdep_search(card, hwdep->device)) {
+ mutex_unlock(&register_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(&register_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(&register_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(&register_mutex);
+ if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) {
+ mutex_unlock(&register_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(&register_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(&register_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(&register_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(&register_mutex);
+ device = snd_pcm_next(card, device);
+ mutex_unlock(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_mutex);
+ if (nfree) {
+ list_del(&notify->list);
+ list_for_each_entry(pcm, &snd_pcm_devices, list)
+ notify->n_unregister(pcm);
+ } else {
+ list_add_tail(&notify->list, &snd_pcm_notify_list);
+ list_for_each_entry(pcm, &snd_pcm_devices, list)
+ notify->n_register(pcm);
+ }
+ mutex_unlock(&register_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(&register_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(&register_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(&params, 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, &params);
+ 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(&params, _params, sizeof(params)))
+ return -EFAULT;
+ err = snd_pcm_sw_params(substream, &params);
+ if (copy_to_user(_params, &params, 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(&register_mutex);
+ ret = __snd_rawmidi_info_select(card, info);
+ mutex_unlock(&register_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(&params, 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, &params);
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ if (rfile->input == NULL)
+ return -EINVAL;
+ return snd_rawmidi_input_params(rfile->input, &params);
+ 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(&register_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(&register_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(&register_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(&register_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(&register_mutex);
+ if (snd_rawmidi_search(rmidi->card, rmidi->device))
+ err = -EBUSY;
+ else
+ list_add_tail(&rmidi->list, &snd_rawmidi_devices);
+ mutex_unlock(&register_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(&register_mutex);
+ list_del(&rmidi->list);
+ mutex_unlock(&register_mutex);
+ return err;
+}
+
+static int snd_rawmidi_dev_disconnect(struct snd_device *device)
+{
+ struct snd_rawmidi *rmidi = device->device_data;
+ int dir;
+
+ mutex_lock(&register_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(&register_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, &params);
+ case SNDRV_RAWMIDI_STREAM_INPUT:
+ if (!rfile->input)
+ return -EINVAL;
+ return snd_rawmidi_input_params(rfile->input, &params);
+ }
+ 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(&register_mutex);
+ rc = snd_seq_oss_open(file, level);
+ mutex_unlock(&register_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(&register_mutex);
+ snd_seq_oss_release(dp);
+ mutex_unlock(&register_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(&register_mutex))
+ return -ERESTARTSYS;
+ rc = snd_seq_oss_ioctl(dp, cmd, arg);
+ if (cmd != SNDCTL_SEQ_SYNC)
+ mutex_unlock(&register_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(&register_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(&register_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(&register_mutex);
+ return rc;
+ }
+ mutex_unlock(&register_mutex);
+ return 0;
+}
+
+static void
+unregister_device(void)
+{
+ mutex_lock(&register_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(&register_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(&register_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(&register_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(&register_lock, flags);
+ mdev = midi_devs[dev];
+ if (mdev)
+ snd_use_lock_use(&mdev->use_lock);
+ spin_unlock_irqrestore(&register_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(&register_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(&register_lock, flags);
+ return mdev;
+ }
+ }
+ spin_unlock_irqrestore(&register_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(&register_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(&register_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(&register_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(&register_lock, flags);
+ midi_devs[mdev->seq_device] = NULL;
+ spin_unlock_irqrestore(&register_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(&register_lock, flags);
+ for (index = max_midi_devs - 1; index >= 0; index--) {
+ if (midi_devs[index])
+ break;
+ }
+ max_midi_devs = index + 1;
+ spin_unlock_irqrestore(&register_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(&register_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(&register_lock, flags);
+}
+
+
+/*
+ * set up midi tables
+ */
+void
+snd_seq_oss_midi_setup(struct seq_oss_devinfo *dp)
+{
+ spin_lock_irq(&register_lock);
+ dp->max_mididev = max_midi_devs;
+ spin_unlock_irq(&register_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(&register_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(&register_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(&register_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(&register_lock, flags);
+ for (index = 0; index < max_synth_devs; index++) {
+ if (synth_devs[index] == rec)
+ break;
+ }
+ if (index >= max_synth_devs) {
+ spin_unlock_irqrestore(&register_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(&register_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(&register_lock, flags);
+ rec = synth_devs[dev];
+ if (rec)
+ snd_use_lock_use(&rec->use_lock);
+ spin_unlock_irqrestore(&register_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(&register_mutex);
+ switch (client->type) {
+ case NO_CLIENT:
+ pr_warn("ALSA: seq: Trying to free unused client %d\n",
+ client->number);
+ break;
+ case USER_CLIENT:
+ case KERNEL_CLIENT:
+ seq_free_client1(client);
+ usage_free(&client_usage, 1);
+ break;
+
+ default:
+ pr_err("ALSA: seq: Trying to free client %d with undefined type = %d\n",
+ client->number, client->type);
+ }
+ mutex_unlock(&register_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(&register_mutex);
+ client = seq_create_client1(-1, SNDRV_SEQ_DEFAULT_EVENTS);
+ if (!client) {
+ mutex_unlock(&register_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(&register_mutex);
+ return -ENOMEM;
+ }
+ }
+
+ usage_alloc(&client_usage, 1);
+ client->type = USER_CLIENT;
+ mutex_unlock(&register_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(&register_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(&register_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(&register_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(&register_mutex);
+ err = snd_register_device(SNDRV_DEVICE_TYPE_SEQUENCER, NULL, 0,
+ &snd_seq_f_ops, NULL, seq_dev);
+ mutex_unlock(&register_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(&params, 0, sizeof(params));
+ params.avail_min = 1;
+ params.buffer_size = input_buffer_size;
+ err = snd_rawmidi_input_params(msynth->input_rfile.input, &params);
+ 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(&params, 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, &params);
+ 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(&register_mutex);
+ client = synths[card->number];
+ if (client == NULL) {
+ newclient = 1;
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (client == NULL) {
+ mutex_unlock(&register_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(&register_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(&register_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(&register_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(&register_mutex);
+ client = synths[card->number];
+ if (client == NULL || client->ports[device] == NULL) {
+ mutex_unlock(&register_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(&register_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(&register_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(&register_mutex);
+ snd_timer_request(tid);
+ mutex_lock(&register_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(&register_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(&register_mutex);
+ snd_timer_close_locked(timeri, &card_dev_to_put);
+ mutex_unlock(&register_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(&register_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(&register_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(&register_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(&register_mutex);
+ return -EBUSY;
+ }
+ list_add_tail(&timer->device_list, &timer1->device_list);
+ mutex_unlock(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&register_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(&params, _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, &params, 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);